├── .gitignore ├── LICENSE ├── README.md ├── init.sql ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── luban │ │ └── demo │ │ ├── LubanApplication.java │ │ ├── common │ │ ├── enums │ │ │ └── UploadEnum.java │ │ ├── exceptions │ │ │ ├── AbstractExceptionTranslator.java │ │ │ ├── ExceptionTranslator.java │ │ │ ├── LubanException.java │ │ │ └── error │ │ │ │ ├── ErrorEnum.java │ │ │ │ ├── ErrorVM.java │ │ │ │ └── FieldVM.java │ │ └── utils │ │ │ └── EnvUtil.java │ │ ├── config │ │ └── Swagger2Config.java │ │ ├── controller │ │ ├── UploadController.java │ │ └── WorkController.java │ │ ├── domain │ │ ├── Work.java │ │ └── WorkForms.java │ │ ├── dto │ │ └── WorkDto.java │ │ ├── repo │ │ └── WorkRepo.java │ │ ├── request │ │ ├── WorkCreateRequest.java │ │ ├── WorkQueryRequest.java │ │ └── WorkUpdateRequest.java │ │ └── service │ │ ├── UploadService.java │ │ ├── WorkService.java │ │ └── impl │ │ ├── UploadServiceImpl.java │ │ └── WorkServiceImpl.java └── resources │ ├── application-dev.example.yml │ ├── application-prod.example.yml │ ├── application.yml │ └── logback.xml └── test └── java └── com └── luban └── demo └── LubanApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | 33 | # https://stackoverflow.com/questions/11145131/how-to-config-gitignore 34 | src/main/resources/application-dev.yml 35 | src/main/resources/application-prod.yml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 luban-h5 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 2 | 3 | # springboot2-jpa-api-for-luban 4 | 5 | * #!zh: 为[鲁班H5](https://github.com/ly525/luban-h5) 提供 由 [Spring Boot](https://spring.io/projects/spring-boot) 驱动的后端 API 6 | * #!zh: 现在仍然在完善中,非常欢迎 PR,如果您想参与贡献,可以直接 Pull Request。也可以和作者直接联系, [联系方式](https://github.com/ly525/luban-h5#%E4%BA%A4%E6%B5%81%E7%BE%A4) 7 | 8 | * #!en: Demo API for [Luban-H5-Editor-Module](https://github.com/ly525/luban-h5) powered by [Spring Boot + JPA](https://spring.io/projects/spring-boot) 9 | * #!en It is still working in progress, so pr is welcome!(now it's just a demo) 10 | 11 | #### 相关文档 12 | * [说明文档(WIP/完善中)](https://www.yuque.com/xpm1xa/rgf7kz/xkm4aq) 13 | 14 | 15 | ### TODO 16 | > pr is welcome! 17 | 18 | - [x] Get Work By Id 19 | - [x] Get All Works 20 | - [x] Update Work 21 | - [x] Create Work 22 | - [x] Delete Work 23 | - [ ] Upload Work Cover 24 | - [ ] Cors Proxy 25 | - [x] Set Work as Template 26 | - [ ] create Work based on Template 27 | 28 | 29 | ### Development 30 | ##### 后端 31 | 1. 使用 `init.sql` 初始化数据(不含建库语句),mysql 版本需要 >= 5.7.8 32 | 2. 修改 `src/main/resources/application-dev.example.yml` 中的 mysql 相关配置 33 | 3. 修改 `src/main/resources/application-prod.example.yml` 中的 mysql 相关配置 34 | 4. `git clone https://github.com/luban-h5/springboot2-jpa-api-for-luban` , 启动 Spring Boot 项目 35 | 36 | 37 | ##### 前端 38 | > 预备知识 39 | > * [Node、yarn 安装教程](https://github.com/ly525/luban-h5/blob/dev/docs/zh/getting-started/quick-start.md#nodeyarnnpm%E5%AE%89%E8%A3%85) 40 | > * 请使用 yarn 安装依赖,而非 npm,原因参见 [#92](https://github.com/ly525/luban-h5/issues/92) 和 [#101](https://github.com/ly525/luban-h5/issues/101) 41 | > * 安装前端项目依赖,[前端开发文档](https://github.com/ly525/luban-h5/blob/dev/docs/zh/getting-started/quick-start.md): 42 | 43 | 44 | ```bash 45 | # 重新打开一个 terminal 46 | git clone https://github.com/ly525/luban-h5 47 | cd luban-h5/front-end/h5 48 | 49 | # 安装前端依赖 50 | yarn install 51 | # #!en modify `target` in `vue.config.js` 52 | # #!zh 修改 vue.config.js 中的 target 变量,比如:const target = 'http://127.0.0.1:8888', 53 | # 其中: 54 | # 8888 为 springboot2-jpa-api-for-luban 提供服务的端口 55 | # 127.0.0.1 是本地开发的IP 或者 内网 IP 都是可以的 56 | 57 | # 修改完毕之后,运行下面的命令,即可启动前端服务进行联调 58 | # 注意是 serve 不是 server! 59 | yarn serve 60 | 61 | ``` 62 | 63 | 联调开始 64 | -------------------------------------------------------------------------------- /init.sql: -------------------------------------------------------------------------------- 1 | 2 | DROP TABLE IF EXISTS `work`; 3 | CREATE TABLE `work` ( 4 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 5 | `title` varchar(255) NOT NULL COMMENT '标题', 6 | `description` longtext COMMENT '描述', 7 | `cover_image_url` longtext, 8 | `pages` json DEFAULT NULL, 9 | `publish` tinyint(1) NOT NULL DEFAULT '0', 10 | `template` tinyint(1) NOT NULL DEFAULT '0', 11 | `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 12 | `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', 13 | PRIMARY KEY (`id`) 14 | ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8; 15 | 16 | 17 | DROP TABLE IF EXISTS `work_forms`; 18 | CREATE TABLE `work_forms` ( 19 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 20 | `form` longtext, 21 | `work_id` bigint(20) NOT NULL, 22 | PRIMARY KEY (`id`), 23 | KEY `work_id` (`work_id`), 24 | CONSTRAINT `work_forms_ibfk_1` FOREIGN KEY (`work_id`) REFERENCES `work` (`id`) 25 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 26 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.1.RELEASE 9 | 10 | 11 | com.luban 12 | demo 13 | 0.0.1-SNAPSHOT 14 | demo 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 5.1.40 20 | 5.1.0.Final 21 | 0.2.7 22 | 1.16.10 23 | 2.5.1 24 | 2.9.2 25 | 1.2.49 26 | 2.0.1 27 | 2.5 28 | 3.4 29 | 3.6 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-data-jpa 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-mustache 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 45 | 46 | mysql 47 | mysql-connector-java 48 | ${mysql-driver.version} 49 | 50 | 51 | com.alibaba 52 | fastjson 53 | ${fastjson.version} 54 | 55 | 56 | org.hibernate 57 | hibernate-spatial 58 | ${hibernate-spatial.version} 59 | 60 | 61 | 62 | com.zaxxer 63 | HikariCP 64 | ${HikariCP.version} 65 | 66 | 67 | org.lazyluke 68 | log4jdbc-remix 69 | ${log4jdbc.version} 70 | 71 | 72 | 73 | org.projectlombok 74 | lombok 75 | ${lombok.version} 76 | true 77 | 78 | 79 | 80 | org.junit.jupiter 81 | junit-jupiter-api 82 | RELEASE 83 | compile 84 | 85 | 86 | 87 | io.springfox 88 | springfox-swagger2 89 | ${springfox.version} 90 | 91 | 92 | 93 | io.springfox 94 | springfox-swagger-ui 95 | ${springfox.version} 96 | 97 | 98 | com.github.xiaoymin 99 | knife4j-spring-boot-starter 100 | ${knife4j.version} 101 | 102 | 103 | commons-io 104 | commons-io 105 | ${commons-io.version} 106 | 107 | 108 | org.apache.commons 109 | commons-lang3 110 | ${commons-lang3.version} 111 | 112 | 113 | commons-net 114 | commons-net 115 | ${commons-net.version} 116 | 117 | 118 | 119 | org.springframework.boot 120 | spring-boot-starter-test 121 | test 122 | 123 | 124 | org.junit.vintage 125 | junit-vintage-engine 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | org.springframework.boot 135 | spring-boot-maven-plugin 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/LubanApplication.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.core.env.Environment; 8 | 9 | import java.net.Inet4Address; 10 | import java.net.InetAddress; 11 | import java.net.NetworkInterface; 12 | import java.net.UnknownHostException; 13 | import java.util.Enumeration; 14 | 15 | /** 16 | * @author Yang Hao 17 | * @date 2019/11/17 21:07 18 | */ 19 | @SpringBootApplication 20 | public class LubanApplication { 21 | 22 | private static Logger log = LoggerFactory.getLogger(LubanApplication.class); 23 | 24 | public static void main(String[] args) throws Exception { 25 | 26 | SpringApplication app = new SpringApplication(LubanApplication.class); 27 | Environment env = app.run(args).getEnvironment(); 28 | String protocol = "http"; 29 | if (env.getProperty("server.ssl.key-store") != null) { 30 | protocol = "https"; 31 | } 32 | 33 | log.info("\n----------------------------------------------------------\n\t" + 34 | "Application '{}' is running! Access URLs:\n\t" + 35 | "Local: \t\t\t\t{}://localhost:{}\n\t" + 36 | "API 地址: \t\t\t{}://{}:{}/doc.html\n\t" + 37 | "Profile(s): \t{}\n----------------------------------------------------------", 38 | env.getProperty("spring.application.name"), 39 | protocol, 40 | env.getProperty("server.port"), 41 | protocol, 42 | getLocalHostLANAddress(), 43 | env.getProperty("server.port"), 44 | env.getActiveProfiles()); 45 | 46 | String configServerStatus = env.getProperty("configserver.status"); 47 | log.info("\n----------------------------------------------------------\n\t" + 48 | "Config Server: \t{}\n----------------------------------------------------------", 49 | configServerStatus == null ? "Not found or not setup for this application" : configServerStatus); 50 | 51 | 52 | } 53 | 54 | private static String getHostIp() { 55 | try { 56 | Enumeration allNetInterfaces = NetworkInterface.getNetworkInterfaces(); 57 | while (allNetInterfaces.hasMoreElements()) { 58 | NetworkInterface netInterface = allNetInterfaces.nextElement(); 59 | Enumeration addresses = netInterface.getInetAddresses(); 60 | while (addresses.hasMoreElements()) { 61 | InetAddress ip = addresses.nextElement(); 62 | if (ip != null 63 | && ip instanceof Inet4Address 64 | && !ip.isLoopbackAddress() //loopback地址即本机地址,IPv4的loopback范围是127.0.0.0 ~ 127.255.255.255 65 | && ip.getHostAddress().indexOf(":") == -1) { 66 | System.out.println("本机的IP = " + ip.getHostAddress()); 67 | return ip.getHostAddress(); 68 | } 69 | } 70 | } 71 | } catch (Exception e) { 72 | e.printStackTrace(); 73 | } 74 | return null; 75 | } 76 | 77 | 78 | // 正确的IP拿法,即优先拿site-local地址 79 | private static InetAddress getLocalHostLANAddress() throws UnknownHostException { 80 | try { 81 | InetAddress candidateAddress = null; 82 | // 遍历所有的网络接口 83 | for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) { 84 | NetworkInterface iface = (NetworkInterface) ifaces.nextElement(); 85 | // 在所有的接口下再遍历IP 86 | for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) { 87 | InetAddress inetAddr = (InetAddress) inetAddrs.nextElement(); 88 | if (!inetAddr.isLoopbackAddress()) {// 排除loopback类型地址 89 | if (inetAddr.isSiteLocalAddress()) { 90 | // 如果是site-local地址,就是它了 91 | return inetAddr; 92 | } else if (candidateAddress == null) { 93 | // site-local类型的地址未被发现,先记录候选地址 94 | candidateAddress = inetAddr; 95 | } 96 | } 97 | } 98 | } 99 | if (candidateAddress != null) { 100 | return candidateAddress; 101 | } 102 | // 如果没有发现 non-loopback地址.只能用最次选的方案 103 | InetAddress jdkSuppliedAddress = InetAddress.getLocalHost(); 104 | if (jdkSuppliedAddress == null) { 105 | throw new UnknownHostException("The JDK InetAddress.getLocalHost() method unexpectedly returned null."); 106 | } 107 | return jdkSuppliedAddress; 108 | } catch (Exception e) { 109 | UnknownHostException unknownHostException = new UnknownHostException( 110 | "Failed to determine LAN address: " + e); 111 | unknownHostException.initCause(e); 112 | throw unknownHostException; 113 | } 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/common/enums/UploadEnum.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.common.enums; 2 | 3 | /** 4 | * @author Yang Hao 5 | * @date 2020/1/12 18:48 6 | */ 7 | public enum UploadEnum { 8 | /** 9 | * 上传目录 10 | */ 11 | work 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/common/exceptions/AbstractExceptionTranslator.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.common.exceptions; 2 | 3 | 4 | import com.luban.demo.common.exceptions.error.ErrorEnum; 5 | import com.luban.demo.common.exceptions.error.ErrorVM; 6 | import com.luban.demo.common.exceptions.error.FieldVM; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.core.annotation.AnnotationUtils; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.http.ResponseEntity.BodyBuilder; 13 | import org.springframework.validation.BindException; 14 | import org.springframework.validation.BindingResult; 15 | import org.springframework.validation.FieldError; 16 | import org.springframework.web.HttpMediaTypeNotSupportedException; 17 | import org.springframework.web.HttpRequestMethodNotSupportedException; 18 | import org.springframework.web.bind.MethodArgumentNotValidException; 19 | import org.springframework.web.bind.annotation.ExceptionHandler; 20 | import org.springframework.web.bind.annotation.ResponseBody; 21 | import org.springframework.web.bind.annotation.ResponseStatus; 22 | 23 | import javax.servlet.http.HttpServletRequest; 24 | import java.nio.file.AccessDeniedException; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | /** 29 | * Controller advice to translate the server side exceptions to client-friendly json structures. 30 | */ 31 | public abstract class AbstractExceptionTranslator { 32 | private final Logger log = LoggerFactory.getLogger(AbstractExceptionTranslator.class); 33 | 34 | 35 | /** 36 | * 处理参数校验异常,多个字段错误转换成错误数组 37 | * 38 | * @param request 39 | * @param ex 40 | * @return 41 | */ 42 | @ExceptionHandler(MethodArgumentNotValidException.class) 43 | @ResponseStatus(HttpStatus.BAD_REQUEST) 44 | @ResponseBody 45 | protected ErrorVM processValidationError(HttpServletRequest request, MethodArgumentNotValidException ex) { 46 | ErrorVM error = getErrorVM(ex.getBindingResult()); 47 | log.error("MethodArgumentNotValidException : [" + error.getErrorId() + "] : ", error.getError(), ex); 48 | return error; 49 | } 50 | 51 | @ExceptionHandler(AccessDeniedException.class) 52 | @ResponseStatus(HttpStatus.FORBIDDEN) 53 | @ResponseBody 54 | protected ErrorVM processAccessDeniedException(AccessDeniedException ex) { 55 | String errorCode = ErrorEnum.ACCESS_DENIED.getError(); 56 | ErrorVM error = new ErrorVM(errorCode, ex.getMessage(), HttpStatus.FORBIDDEN.value()); 57 | error.setCause(ex.getMessage()); 58 | log.error("AccessDeniedException[" + error.getErrorId() + "]: " + ex.getMessage(), ex); 59 | return error; 60 | } 61 | 62 | @ExceptionHandler(HttpRequestMethodNotSupportedException.class) 63 | @ResponseBody 64 | @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) 65 | protected ErrorVM processMethodNotSupportedException(HttpRequestMethodNotSupportedException ex) { 66 | String errorCode = ErrorEnum.ERR_METHOD_NOT_SUPPORTED.getError(); 67 | ErrorVM error = new ErrorVM(errorCode, ex.getMessage(), HttpStatus.METHOD_NOT_ALLOWED.value()); 68 | log.error("HttpRequestMethodNotSupportedException[" + error.getErrorId() + "]: " + ex.getMessage(), ex); 69 | return error; 70 | } 71 | 72 | @ExceptionHandler(IllegalArgumentException.class) 73 | @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) 74 | protected ErrorVM handleIllegalArgumentException(IllegalArgumentException ex) { 75 | String errorCode = ErrorEnum.UN_PROCESSABLE_ENTITY.getError(); 76 | ErrorVM error = new ErrorVM(errorCode, ex.getMessage(), HttpStatus.UNPROCESSABLE_ENTITY.value()); 77 | log.error("IllegalArgumentException[" + error.getErrorId() + "]: " + ex.getMessage(), ex); 78 | return error; 79 | } 80 | 81 | @ExceptionHandler(LubanException.class) 82 | @ResponseStatus(HttpStatus.BAD_REQUEST) 83 | @ResponseBody 84 | protected ErrorVM processServiceError(LubanException ex) { 85 | ErrorVM error = new ErrorVM(ex.getError(), ex.getMessage(), HttpStatus.BAD_REQUEST.value()); 86 | log.error("AconnException[" + error.getErrorId() + "]: " + ex.getMessage(), ex); 87 | return error; 88 | } 89 | 90 | @ExceptionHandler(BindException.class) 91 | @ResponseStatus(HttpStatus.BAD_REQUEST) 92 | @ResponseBody 93 | protected ErrorVM processBindException(BindException ex) { 94 | ErrorVM error = getErrorVM(ex.getBindingResult()); 95 | log.error("BindException[" + error.getErrorId() + "]: " + ex.getMessage(), ex); 96 | return error; 97 | } 98 | 99 | @ExceptionHandler(HttpMediaTypeNotSupportedException.class) 100 | @ResponseStatus(HttpStatus.BAD_REQUEST) 101 | protected ErrorVM handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException ex) { 102 | String errorCode = ErrorEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED.getError(); 103 | ErrorVM error = new ErrorVM(errorCode, 104 | ErrorEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED.getDesc(), 105 | HttpStatus.BAD_REQUEST.value()); 106 | log.error("HttpMediaTypeNotSupportedException[" + error.getErrorId() + "]: " + ex.getMessage(), ex); 107 | return error; 108 | } 109 | 110 | 111 | @ExceptionHandler(Exception.class) 112 | protected ResponseEntity processException(Exception ex) { 113 | BodyBuilder builder; 114 | ErrorVM error; 115 | ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class); 116 | if (responseStatus != null) { 117 | builder = ResponseEntity.status(responseStatus.value()); 118 | String errorCode = "error." + responseStatus.value().value(); 119 | error = new ErrorVM(errorCode, ex.getMessage(), responseStatus.reason(), responseStatus.value().value()); 120 | } else { 121 | builder = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR); 122 | String errorCode = ErrorEnum.INTERNAL_SERVER_ERROR.getError(); 123 | error = new ErrorVM(errorCode, ErrorEnum.INTERNAL_SERVER_ERROR.getDesc()); 124 | error.setCause(ex.getMessage()); 125 | } 126 | log.error("Exception[" + error.getErrorId() + "]: " + ex.getMessage(), ex); 127 | return builder.body(error); 128 | } 129 | 130 | 131 | protected ErrorVM getErrorVM(BindingResult bindingResult) { 132 | BindingResult result = bindingResult; 133 | List fieldErrors = result.getFieldErrors(); 134 | 135 | List fieldVMS = new ArrayList(fieldErrors.size()); 136 | 137 | for (FieldError fieldError : fieldErrors) { 138 | String objectName = fieldError.getObjectName(); 139 | String field = fieldError.getField(); 140 | String defaultMessage = fieldError.getDefaultMessage(); 141 | String code = fieldError.getCode(); 142 | fieldVMS.add(new FieldVM(objectName, field, code, defaultMessage, null)); 143 | } 144 | String errorCode = ErrorEnum.METHOD_ARGUMENT_NOT_VALID.getError(); 145 | return new ErrorVM(errorCode, errorCode, null, HttpStatus.BAD_REQUEST.value(), fieldVMS); 146 | } 147 | 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/common/exceptions/ExceptionTranslator.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.common.exceptions; 2 | 3 | 4 | import com.luban.demo.common.exceptions.error.ErrorEnum; 5 | import com.luban.demo.common.exceptions.error.ErrorVM; 6 | import com.luban.demo.common.exceptions.error.FieldVM; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.util.CollectionUtils; 9 | import org.springframework.validation.BindingResult; 10 | import org.springframework.validation.FieldError; 11 | import org.springframework.web.bind.annotation.ControllerAdvice; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * Controller advice to translate the server side exceptions to client-friendly json structures. 18 | */ 19 | @ControllerAdvice 20 | public class ExceptionTranslator extends AbstractExceptionTranslator { 21 | 22 | 23 | @Override 24 | public ErrorVM getErrorVM(BindingResult bindingResult) { 25 | BindingResult result = bindingResult; 26 | List fieldErrors = result.getFieldErrors(); 27 | 28 | List fieldVMS = new ArrayList(fieldErrors.size()); 29 | 30 | for (FieldError fieldError : fieldErrors) { 31 | String objectName = fieldError.getObjectName(); 32 | String field = fieldError.getField(); 33 | String defaultMessage = fieldError.getDefaultMessage(); 34 | String code = fieldError.getCode(); 35 | fieldVMS.add(new FieldVM(objectName, field, code, defaultMessage, null)); 36 | } 37 | if (!CollectionUtils.isEmpty(fieldErrors)) { 38 | String errorCode = fieldErrors.get(0).getCode(); 39 | String message = fieldErrors.get(0).getDefaultMessage(); 40 | return new ErrorVM(errorCode, message, null, HttpStatus.BAD_REQUEST.value(), fieldVMS); 41 | } 42 | String errorCode = ErrorEnum.METHOD_ARGUMENT_NOT_VALID.getError(); 43 | return new ErrorVM(errorCode, ErrorEnum.METHOD_ARGUMENT_NOT_VALID.getDesc(), null, HttpStatus.BAD_REQUEST.value(), fieldVMS); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/common/exceptions/LubanException.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.common.exceptions; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * Lu banException鲁班异常 7 | * 8 | * @author Yang Hao 9 | * @date 2020/1/12 17:40 10 | */ 11 | @Data 12 | public class LubanException extends RuntimeException { 13 | 14 | private static final long serialVersionUID = -3576785908021342999L; 15 | 16 | private String error; 17 | 18 | public LubanException() { 19 | super(); 20 | } 21 | 22 | public LubanException(String error) { 23 | super(error); 24 | this.error = error; 25 | } 26 | 27 | public LubanException(String error, String message) { 28 | super(message); 29 | this.error = error; 30 | } 31 | 32 | public LubanException(String message, Throwable cause) { 33 | super(message, cause); 34 | } 35 | 36 | public LubanException(Throwable cause) { 37 | super(cause); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/common/exceptions/error/ErrorEnum.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.common.exceptions.error; 2 | 3 | public enum ErrorEnum { 4 | 5 | METHOD_ARGUMENT_NOT_VALID("error.common.argumentNotValid", "方法参数无效"), 6 | ACCESS_DENIED("error.common.accessDenied", "进入拒绝"), 7 | INTERNAL_SERVER_ERROR("error.common.internalServerError", "服务内部错误"), 8 | ERR_METHOD_NOT_SUPPORTED("error.common.methodNotSupported", "方法不支持"), 9 | UNAUTHORIZED("error.common.unauthorized", "未授权"), 10 | BAD_CREDENTIALS("error.common.badCredentials", "账号或密码错误"), 11 | HTTP_MEDIA_TYPE_NOT_SUPPORTED("error.common.mediaTypeNotSupported", "请求Content-Type不支持"), 12 | UN_PROCESSABLE_ENTITY("error.common.unProcessableEntity", "无法处理的实体类"), 13 | NAME_PASSWORD_INCORRECT("user.username.password.incorrect", "用户名或密码错误"), 14 | USER_NOT_EXIST("user.notExist", "用户不存在"), 15 | USER_LOCKED("user.lock", "用户已被锁定"); 16 | 17 | private String error; 18 | private String desc; 19 | 20 | ErrorEnum(String error, String desc) { 21 | this.error = error; 22 | this.desc = desc; 23 | } 24 | 25 | public String getError() { 26 | return this.error; 27 | } 28 | 29 | public String getDesc() { 30 | return this.desc; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/common/exceptions/error/ErrorVM.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.common.exceptions.error; 2 | 3 | import lombok.Data; 4 | import org.apache.commons.lang3.RandomStringUtils; 5 | 6 | import java.io.Serializable; 7 | import java.util.List; 8 | 9 | /** 10 | * 用于报错后错误返回 11 | */ 12 | @Data 13 | public class ErrorVM implements Serializable { 14 | private String errorId; 15 | private String entityName; //错误实体 16 | private String error; //错误 17 | private String cause; //错误原因 18 | private Integer status; //错误状态码 19 | private String message; //错误信息 20 | private List fields; //错误字段 21 | 22 | public ErrorVM(String error, String description) { 23 | this(error, description, null); 24 | } 25 | 26 | public ErrorVM(String error, String description, Integer status) { 27 | this(error, description, null, status, null); 28 | } 29 | 30 | public ErrorVM(String error, String description, String cause, Integer status) { 31 | this(error, description, cause, status, null); 32 | } 33 | 34 | public ErrorVM(String error, String message, String cause, Integer status, List fields) { 35 | this.errorId = RandomStringUtils.randomNumeric(8); 36 | this.error = error; 37 | this.message = message; 38 | this.status = status; 39 | this.fields = fields; 40 | this.cause = cause; 41 | // this.entityName = this.getClass().getSimpleName(); 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/common/exceptions/error/FieldVM.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.common.exceptions.error; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | 8 | /** 9 | * 用于校验错误字段描述 10 | * 11 | * @author Yang Hao 12 | * @date 2020/1/12 17:40 13 | */ 14 | @Data 15 | public class FieldVM implements Serializable { 16 | private String objectName; //实体名称 17 | private String field; //字段 18 | private String message; //错误描述 19 | private String defaultMessage; 20 | private Object[] arguments; //参数 21 | 22 | public FieldVM(String objectName, String field, String message, String defaultMessage, Object[] arguments) { 23 | this.objectName = objectName; 24 | this.field = field; 25 | this.message = message; 26 | this.defaultMessage = defaultMessage; 27 | this.arguments = arguments; 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/common/utils/EnvUtil.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.common.utils; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.springframework.core.env.Environment; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * 获取环境变量 11 | * 12 | * @author Yang Hao 13 | * @date 2020/1/12 18:40 14 | */ 15 | public class EnvUtil { 16 | 17 | private static final Map cache = new HashMap(16); 18 | 19 | public static String getStrWithCache(Environment env, String key) { 20 | return getStrWithCache(env, key, null); 21 | } 22 | 23 | public static String getStrWithCache(Environment env, String key, String defaul) { 24 | String val = cache.get(key); 25 | if (StringUtils.isNotEmpty(val)) { 26 | return cache.get(key); 27 | } 28 | 29 | val = env.getProperty(key); 30 | if (StringUtils.isNotEmpty(val)) { 31 | cache.put(key, val); 32 | } else { 33 | val = defaul; 34 | } 35 | return val; 36 | } 37 | 38 | /** 39 | * 获取环境变量中int类型属性值 40 | * 41 | * @param env 42 | * @param key 43 | * @param defaul 44 | * @return 45 | */ 46 | public static Integer getIntWithCache(Environment env, String key, Integer defaul) { 47 | Integer res = env.getProperty(key, Integer.class); 48 | return res == null ? defaul : res; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/config/Swagger2Config.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.config; 2 | 3 | import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import springfox.documentation.builders.ApiInfoBuilder; 7 | import springfox.documentation.builders.PathSelectors; 8 | import springfox.documentation.builders.RequestHandlerSelectors; 9 | import springfox.documentation.service.ApiInfo; 10 | import springfox.documentation.service.Contact; 11 | import springfox.documentation.spi.DocumentationType; 12 | import springfox.documentation.spring.web.plugins.Docket; 13 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 14 | 15 | /** 16 | * @author Yang Hao 17 | * @date 2019/11/17 20:58 18 | */ 19 | @Configuration 20 | @EnableSwagger2 21 | @EnableKnife4j 22 | public class Swagger2Config { 23 | 24 | @Bean 25 | public Docket petApi() { 26 | 27 | return new Docket(DocumentationType.SWAGGER_2) 28 | .apiInfo(apiInfo()) 29 | .select() 30 | //当前包路径 31 | .apis(RequestHandlerSelectors.basePackage("com.luban.demo.controller")) 32 | .paths(PathSelectors.any()) 33 | .build(); 34 | 35 | } 36 | 37 | /** 38 | * 构建api文档的详细信息函数 39 | * 40 | * @return 41 | */ 42 | private ApiInfo apiInfo() { 43 | return new ApiInfoBuilder() 44 | //页面标题 45 | .title("API 描述") 46 | //创建人 47 | .contact(new Contact("springboot2-jpa-api-for-luban", "https://github.com/luban-h5/springboot2-jpa-api-for-luban", "")) 48 | //版本号 49 | .version("1.0") 50 | //描述 51 | .description("API 描述") 52 | .termsOfServiceUrl("http://localhost/") 53 | .build(); 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/controller/UploadController.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.controller; 2 | 3 | import com.luban.demo.service.UploadService; 4 | import io.swagger.annotations.Api; 5 | import io.swagger.annotations.ApiOperation; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import org.springframework.web.multipart.MultipartFile; 12 | 13 | import javax.annotation.Resource; 14 | import javax.validation.constraints.NotNull; 15 | 16 | /** 17 | * @author Yang Hao 18 | * @date 2020/1/12 17:40 19 | */ 20 | @RestController 21 | @RequestMapping(value = "/upload") 22 | @Api(value = "文件上传", tags = {"文件上传"}) 23 | @Slf4j 24 | public class UploadController { 25 | 26 | @Resource 27 | private UploadService uploadService; 28 | 29 | @ApiOperation(value = "文件上传") 30 | @PostMapping 31 | public void upload(@NotNull @RequestParam(value = "file", required = true) MultipartFile file) { 32 | uploadService.upload(file); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/controller/WorkController.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.controller; 2 | 3 | import com.luban.demo.dto.WorkDto; 4 | import com.luban.demo.request.WorkCreateRequest; 5 | import com.luban.demo.request.WorkQueryRequest; 6 | import com.luban.demo.request.WorkUpdateRequest; 7 | import com.luban.demo.service.WorkService; 8 | import io.swagger.annotations.Api; 9 | import io.swagger.annotations.ApiOperation; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.beans.BeanUtils; 12 | import org.springframework.data.domain.Page; 13 | import org.springframework.data.domain.Pageable; 14 | import org.springframework.data.domain.Sort; 15 | import org.springframework.data.web.PageableDefault; 16 | import org.springframework.http.HttpStatus; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import javax.annotation.Resource; 20 | import javax.validation.Valid; 21 | import java.util.List; 22 | 23 | /** 24 | * @author Yang Hao 25 | * @date 2019/11/17 20:42 26 | */ 27 | @RestController 28 | @RequestMapping(value = "/works") 29 | @Api(value = "作品管理", tags = {"作品管理"}) 30 | @Slf4j 31 | public class WorkController { 32 | @Resource 33 | private WorkService workService; 34 | 35 | @ApiOperation("根据workId查询") 36 | @RequestMapping(value = "/{id}", method = RequestMethod.GET) 37 | public WorkDto findWorkById(@PathVariable Long id) { 38 | return workService.findWorkById(id); 39 | } 40 | 41 | /** 42 | * 查询所有work 43 | * 44 | * @param request 45 | * @return 46 | */ 47 | @ApiOperation("查询所有work") 48 | @RequestMapping(method = RequestMethod.GET) 49 | public List listAllWorks(@Valid @ModelAttribute WorkQueryRequest request) { 50 | WorkDto dto = new WorkDto(); 51 | dto.setTemplate(Boolean.valueOf(request.getIs_template())); 52 | BeanUtils.copyProperties(request, dto); 53 | return workService.listAllWorks(dto); 54 | } 55 | 56 | /** 57 | * 分页查询works 58 | * 59 | * @param request 60 | * @return 61 | */ 62 | @ApiOperation("分页查询works") 63 | @RequestMapping(value = "/pageable", method = RequestMethod.GET) 64 | public Page listWorks(@Valid @ModelAttribute WorkQueryRequest request, 65 | @PageableDefault(sort = {"createdTime"}, direction = Sort.Direction.DESC) Pageable pageable) { 66 | WorkDto dto = new WorkDto(); 67 | dto.setTemplate(Boolean.valueOf(request.getIs_template())); 68 | BeanUtils.copyProperties(request, dto); 69 | return workService.listWorks(dto, pageable); 70 | } 71 | 72 | /** 73 | * ResponseStatus 和strapi.js(鲁班官方后端框架) response保持一致 74 | * 75 | * @param request 76 | * @return 77 | */ 78 | @ApiOperation("创建work") 79 | @RequestMapping(method = RequestMethod.POST) 80 | @ResponseStatus(HttpStatus.OK) 81 | public WorkDto createWork(@RequestBody @Valid WorkCreateRequest request) { 82 | WorkDto workDto = new WorkDto(); 83 | BeanUtils.copyProperties(request, workDto); 84 | return workService.createWork(workDto); 85 | } 86 | 87 | /** 88 | * 根据workId 修改work 89 | * 90 | * @param id 91 | * @param request 92 | * @return 93 | */ 94 | @ApiOperation("更新work") 95 | @RequestMapping(value = "/{id}", method = RequestMethod.PUT) 96 | @ResponseStatus(HttpStatus.OK) 97 | public WorkDto updateWork(@PathVariable Long id, @RequestBody @Valid WorkUpdateRequest request) { 98 | WorkDto workDto = new WorkDto(); 99 | workDto.setId(id); 100 | BeanUtils.copyProperties(request, workDto); 101 | return workService.updateWork(workDto); 102 | } 103 | 104 | /** 105 | * 删除work 106 | * 107 | * @param id 108 | * @return 109 | */ 110 | @ApiOperation("删除work") 111 | @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) 112 | public void deleteWorkById(@PathVariable Long id) { 113 | workService.deleteWorkById(id); 114 | } 115 | 116 | /** 117 | * 设为模板 118 | * 119 | * @param id 120 | * @return 121 | */ 122 | @ApiOperation("设为模板") 123 | @RequestMapping(value = "/set-as-template/{id}", method = RequestMethod.POST) 124 | @ResponseStatus(HttpStatus.OK) 125 | public WorkDto markWorkAsTemplate(@PathVariable Long id) { 126 | return workService.markWorkAsTemplate(id); 127 | } 128 | 129 | /** 130 | * 统计作品总数 131 | * 132 | * @return 133 | */ 134 | @ApiOperation("统计作品总数") 135 | @RequestMapping(value = "/count", method = RequestMethod.GET) 136 | public Long countWork() { 137 | return workService.countWork(); 138 | } 139 | 140 | 141 | /** 142 | * 设为模板 143 | * 144 | * @param id 145 | * @return 146 | */ 147 | @ApiOperation("使用模板") 148 | @RequestMapping(value = "/use-template/{id}", method = RequestMethod.POST) 149 | @ResponseStatus(HttpStatus.OK) 150 | public WorkDto useTemplate(@PathVariable Long id) { 151 | return workService.useTemplate(id); 152 | } 153 | 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/domain/Work.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.domain; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.*; 6 | import java.util.Date; 7 | 8 | /** 9 | * @author Yang Hao 10 | * @date 2019/11/17 19:51 11 | */ 12 | @Data 13 | @Entity 14 | @Table(name = "work") 15 | public class Work implements Cloneable { 16 | 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | @Column(columnDefinition = "bigint(11) AUTO_INCREMENT NOT NULL COMMENT '主键'") 20 | private Long id; 21 | 22 | @Column(name = "title") 23 | private String title; 24 | 25 | @Column(name = "description") 26 | private String description; 27 | 28 | @Column(name = "cover_image_url") 29 | private String coverImageUrl; 30 | 31 | @Column(name = "pages") 32 | private String pages; 33 | 34 | @Temporal(TemporalType.TIMESTAMP) 35 | @Column(name = "create_time") 36 | private Date createTime; 37 | 38 | @Temporal(TemporalType.TIMESTAMP) 39 | @Column(name = "update_time") 40 | private Date updateTime; 41 | 42 | @Column(name = "publish") 43 | private boolean publish; 44 | 45 | @Column(name = "template") 46 | private boolean template; 47 | 48 | @Override 49 | public Work clone() throws CloneNotSupportedException { 50 | Work cloneWork = (Work) super.clone(); 51 | cloneWork.setId(null); 52 | cloneWork.setTemplate(false); 53 | cloneWork.setPublish(false); 54 | return cloneWork; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/domain/WorkForms.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.domain; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.*; 6 | 7 | /** 8 | * @author Yang Hao 9 | * @date 2019/11/17 20:36 10 | */ 11 | @Data 12 | @Entity 13 | @Table(name = "work") 14 | public class WorkForms { 15 | 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | @Column(columnDefinition = "bigint(20) AUTO_INCREMENT NOT NULL COMMENT '主键'") 19 | private Long id; 20 | 21 | @Column(name = "form") 22 | private String form; 23 | 24 | @Column(name = "work_id") 25 | private Long workId; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/dto/WorkDto.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | /** 9 | * @author Yang Hao 10 | * @date 2019/11/17 21:48 11 | */ 12 | @Data 13 | public class WorkDto { 14 | 15 | private Long id; 16 | 17 | private String title; 18 | 19 | private String description; 20 | 21 | private String coverImageUrl; 22 | 23 | private List pages; 24 | 25 | private Date createTime; 26 | 27 | private Date updateTime; 28 | 29 | private boolean publish; 30 | 31 | private boolean template; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/repo/WorkRepo.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.repo; 2 | 3 | import com.luban.demo.domain.Work; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 6 | 7 | /** 8 | * @author Yang Hao 9 | * @date 2019/11/17 20:51 10 | */ 11 | public interface WorkRepo extends JpaRepository, JpaSpecificationExecutor { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/request/WorkCreateRequest.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.request; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | /** 9 | * @author Yang Hao 10 | * @date 2019/11/17 21:07 11 | */ 12 | @Data 13 | public class WorkCreateRequest { 14 | 15 | private String title; 16 | 17 | private String description; 18 | 19 | private String coverImageUrl; 20 | 21 | private List pages; 22 | 23 | private Date createTime = new Date(); 24 | 25 | private Date updateTime = new Date(); 26 | 27 | private boolean publish = false; 28 | 29 | private boolean template = false; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/request/WorkQueryRequest.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.request; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | /** 9 | * @author Yang Hao 10 | * @date 2019/11/17 21:07 11 | */ 12 | @Data 13 | public class WorkQueryRequest { 14 | 15 | private String title; 16 | 17 | private String description; 18 | 19 | private String coverImageUrl; 20 | 21 | private List pages; 22 | 23 | private Date createTime; 24 | 25 | private Date updateTime; 26 | 27 | private String is_publish; 28 | 29 | private String is_template; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/request/WorkUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.request; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | /** 9 | * @author Yang Hao 10 | * @date 2019/11/17 21:07 11 | */ 12 | @Data 13 | public class WorkUpdateRequest { 14 | 15 | private String title; 16 | 17 | private String description; 18 | 19 | private String coverImageUrl; 20 | 21 | private List pages; 22 | 23 | private Date updateTime = new Date(); 24 | 25 | private boolean publish; 26 | 27 | private boolean template; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/service/UploadService.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.service; 2 | 3 | import org.springframework.web.multipart.MultipartFile; 4 | 5 | /** 6 | * @author Yang Hao 7 | * @date 2020/1/12 18:09 8 | */ 9 | public interface UploadService { 10 | /** 11 | * 上传文件 12 | * 13 | * @param file 14 | */ 15 | void upload(MultipartFile file); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/service/WorkService.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.service; 2 | 3 | import com.luban.demo.dto.WorkDto; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author Yang Hao 11 | * @date 2019/11/17 20:47 12 | */ 13 | public interface WorkService { 14 | 15 | /** 16 | * 查询所有work 列表,暂不考虑分页 17 | * 18 | * @param dto 19 | * @return 20 | */ 21 | List listAllWorks(WorkDto dto); 22 | 23 | /** 24 | * 分页查询works 25 | * 26 | * @param dto 27 | * @param pageable 28 | * @return 29 | */ 30 | Page listWorks(WorkDto dto, Pageable pageable); 31 | 32 | /** 33 | * 创建work 34 | * 35 | * @param workDto 36 | * @return 37 | */ 38 | WorkDto createWork(WorkDto workDto); 39 | 40 | /** 41 | * 更新work 42 | * 43 | * @param workDto 44 | * @return 45 | */ 46 | WorkDto updateWork(WorkDto workDto); 47 | 48 | /** 49 | * 根据Id 查询work 50 | * 51 | * @param id 52 | * @return 53 | */ 54 | WorkDto findWorkById(Long id); 55 | 56 | /** 57 | * 删除作品 58 | * 59 | * @param id 60 | */ 61 | void deleteWorkById(Long id); 62 | 63 | /** 64 | * 设置为模板 65 | * 66 | * @param id 67 | * @return 68 | */ 69 | WorkDto markWorkAsTemplate(Long id); 70 | 71 | /** 72 | * 统计作品总数 73 | * 74 | * @return 75 | */ 76 | Long countWork(); 77 | 78 | /** 79 | * 使用模板 80 | * 81 | * @param id 82 | * @return 83 | */ 84 | WorkDto useTemplate(Long id); 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/service/impl/UploadServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.service.impl; 2 | 3 | import com.luban.demo.common.enums.UploadEnum; 4 | import com.luban.demo.common.exceptions.LubanException; 5 | import com.luban.demo.common.utils.EnvUtil; 6 | import com.luban.demo.service.UploadService; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.commons.net.ftp.FTP; 9 | import org.apache.commons.net.ftp.FTPClient; 10 | import org.springframework.core.env.Environment; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.web.multipart.MultipartFile; 13 | 14 | import javax.annotation.PostConstruct; 15 | import javax.annotation.Resource; 16 | import java.io.File; 17 | import java.io.FileInputStream; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.nio.file.Files; 21 | import java.nio.file.Path; 22 | import java.nio.file.Paths; 23 | import java.nio.file.StandardCopyOption; 24 | 25 | /** 26 | * FIXME 是否需要文件服务器 27 | * 28 | * @author Yang Hao 29 | * @date 2020/1/12 18:09 30 | */ 31 | @Service 32 | @Slf4j 33 | public class UploadServiceImpl implements UploadService { 34 | 35 | @Resource 36 | private Environment env; 37 | private String ftpHost; 38 | private Integer ftpPort; 39 | private String username; 40 | private String password; 41 | private String localDir; 42 | 43 | @PostConstruct 44 | public void init() { 45 | ftpHost = EnvUtil.getStrWithCache(env, "upload.ftp.host", "127.0.0.1"); 46 | ftpPort = EnvUtil.getIntWithCache(env, "upload.ftp.port", 21); 47 | username = EnvUtil.getStrWithCache(env, "upload.ftp.username", "username"); 48 | password = EnvUtil.getStrWithCache(env, "upload.ftp.password", "password"); 49 | localDir = EnvUtil.getStrWithCache(env, "upload.local.dir", "/opt"); 50 | } 51 | 52 | 53 | @Override 54 | public void upload(MultipartFile file) { 55 | 56 | if (file == null || file.isEmpty()) { 57 | throw new LubanException("请上传文件"); 58 | } 59 | 60 | try { 61 | Path path = getLocalPath(localDir, file.getOriginalFilename()); 62 | path = Files.createDirectories(path); 63 | Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING); 64 | log.info("software upload save to local success"); 65 | } catch (Exception e) { 66 | log.error("software upload save to local failed"); 67 | } 68 | 69 | //FIXME 是否需要上传服务器,本地已保存 70 | FTPClient ftpClient = new FTPClient(); 71 | try { 72 | boolean ok = conectFTP(ftpClient); 73 | if (!ok) { 74 | log.error("ftp login failed,username:{},password:{}", username, password); 75 | return; 76 | } 77 | log.info("ftp login success"); 78 | changeDirectory(ftpClient); 79 | ftpClient.setFileType(FTP.BINARY_FILE_TYPE); 80 | String localPathString = getLocalPathString(localDir, file.getOriginalFilename()); 81 | InputStream inputStream = new FileInputStream(new File(localPathString)); 82 | boolean uploaded = ftpClient.storeFile(file.getOriginalFilename(), inputStream); 83 | inputStream.close(); 84 | 85 | } catch (IOException e) { 86 | log.error("software upload file:{} to ftp server failed", file); 87 | } finally { 88 | if (ftpClient.isConnected()) { 89 | try { 90 | ftpClient.logout(); 91 | ftpClient.disconnect(); 92 | } catch (IOException e) { 93 | log.error("ftp client disconnect error"); 94 | } 95 | } 96 | } 97 | } 98 | 99 | 100 | /** 101 | * 切换文件夹 102 | * 103 | * @param ftpClient 104 | * @throws IOException 105 | */ 106 | private void changeDirectory(FTPClient ftpClient) throws IOException { 107 | if (!ftpClient.changeWorkingDirectory(UploadEnum.work.name())) { 108 | log.info("ftp create ota dir:{}", UploadEnum.work.name()); 109 | ftpClient.makeDirectory(UploadEnum.work.name()); 110 | } 111 | ftpClient.changeWorkingDirectory(UploadEnum.work.name()); 112 | } 113 | 114 | 115 | /** 116 | * ftp连接 117 | * 118 | * @param ftpClient 119 | * @return 120 | * @throws IOException 121 | */ 122 | private boolean conectFTP(FTPClient ftpClient) throws IOException { 123 | ftpClient.connect(ftpHost, ftpPort); 124 | ftpClient.enterLocalPassiveMode(); 125 | return ftpClient.login(username, password); 126 | } 127 | 128 | public Path getLocalPath(String localDir, String file) { 129 | return Paths.get(getLocalPathString(localDir, file)); 130 | } 131 | 132 | public String getLocalPathString(String localDir, String file) { 133 | StringBuilder sb = new StringBuilder(); 134 | sb.append(localDir); 135 | sb.append("/"); 136 | sb.append(file); 137 | return sb.toString(); 138 | } 139 | 140 | 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/luban/demo/service/impl/WorkServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo.service.impl; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.luban.demo.domain.Work; 5 | import com.luban.demo.dto.WorkDto; 6 | import com.luban.demo.repo.WorkRepo; 7 | import com.luban.demo.service.WorkService; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.BeanUtils; 10 | import org.springframework.data.domain.Example; 11 | import org.springframework.data.domain.Page; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.util.CollectionUtils; 15 | import org.springframework.util.StringUtils; 16 | 17 | import javax.annotation.Resource; 18 | import java.util.Date; 19 | import java.util.List; 20 | import java.util.Optional; 21 | import java.util.stream.Collectors; 22 | 23 | /** 24 | * @author Yang Hao 25 | * @date 2019/11/17 20:48 26 | */ 27 | @Service 28 | @Slf4j 29 | public class WorkServiceImpl implements WorkService { 30 | 31 | @Resource 32 | WorkRepo workRepo; 33 | 34 | @Override 35 | public List listAllWorks(WorkDto dto) { 36 | Work work = toWork(dto); 37 | Example example = Example.of(work); 38 | List works = workRepo.findAll(example); 39 | return works.stream().map(this::toWorkDto).collect(Collectors.toList()); 40 | } 41 | 42 | /** 43 | * TODO 暂无业务驱动 44 | * 45 | * @param dto 46 | * @param pageable 47 | * @return 48 | */ 49 | @Override 50 | public Page listWorks(WorkDto dto, Pageable pageable) { 51 | return null; 52 | } 53 | 54 | @Override 55 | public WorkDto createWork(WorkDto workDto) { 56 | Work work = toWork(workDto); 57 | return toWorkDto(workRepo.save(work)); 58 | } 59 | 60 | @Override 61 | public WorkDto updateWork(WorkDto dto) { 62 | Optional result = workRepo.findById(dto.getId()); 63 | 64 | if (result.get() == null) { 65 | return null; 66 | } 67 | 68 | Work work = result.get(); 69 | if (!StringUtils.isEmpty(dto.getTitle())) { 70 | work.setTitle(dto.getTitle()); 71 | } 72 | if (!StringUtils.isEmpty(dto.getDescription())) { 73 | work.setDescription(dto.getDescription()); 74 | } 75 | if (!StringUtils.isEmpty(dto.getCoverImageUrl())) { 76 | work.setCoverImageUrl(dto.getCoverImageUrl()); 77 | } 78 | if (!CollectionUtils.isEmpty(dto.getPages())) { 79 | work.setPages(JSON.toJSONString(dto.getPages())); 80 | } 81 | if (dto.isTemplate()) { 82 | work.setTemplate(true); 83 | } 84 | if (dto.isPublish()) { 85 | work.setPublish(true); 86 | } 87 | work.setUpdateTime(new Date()); 88 | return toWorkDto(workRepo.save(work)); 89 | } 90 | 91 | @Override 92 | public WorkDto findWorkById(Long id) { 93 | Optional result = workRepo.findById(id); 94 | return result.isPresent() ? toWorkDto(result.get()) : null; 95 | } 96 | 97 | @Override 98 | public void deleteWorkById(Long id) { 99 | workRepo.deleteById(id); 100 | } 101 | 102 | @Override 103 | public WorkDto markWorkAsTemplate(Long id) { 104 | 105 | Optional result = workRepo.findById(id); 106 | if (result.get() == null) { 107 | return null; 108 | } 109 | Work work = result.get(); 110 | work.setTemplate(true); 111 | work.setUpdateTime(new Date()); 112 | return toWorkDto(workRepo.save(work)); 113 | } 114 | 115 | @Override 116 | public Long countWork() { 117 | return workRepo.count(); 118 | } 119 | 120 | @Override 121 | public WorkDto useTemplate(Long id) { 122 | Optional result = workRepo.findById(id); 123 | Work work = result.isPresent() ? result.get() : null; 124 | 125 | Work saveWork = new Work(); 126 | try { 127 | saveWork = work.clone(); 128 | } catch (CloneNotSupportedException e) { 129 | e.printStackTrace(); 130 | } 131 | 132 | return toWorkDto(workRepo.save(saveWork)); 133 | } 134 | 135 | private Work toWork(WorkDto workDto) { 136 | Work work = new Work(); 137 | BeanUtils.copyProperties(workDto, work); 138 | if (!CollectionUtils.isEmpty(workDto.getPages())) { 139 | work.setPages(JSON.toJSONString(workDto.getPages())); 140 | } 141 | return work; 142 | } 143 | 144 | private WorkDto toWorkDto(Work work) { 145 | WorkDto workDto = new WorkDto(); 146 | BeanUtils.copyProperties(work, workDto); 147 | if (!StringUtils.isEmpty(work.getPages())) { 148 | workDto.setPages(JSON.parseArray(work.getPages())); 149 | } 150 | return workDto; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.example.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8099 3 | 4 | # =============================== 5 | # = DATA SOURCE 6 | # =============================== 7 | 8 | spring: 9 | datasource: 10 | # driver-class-name: com.mysql.jdbc.Driver 11 | driver-class-name: net.sf.log4jdbc.DriverSpy 12 | type: com.zaxxer.hikari.HikariDataSource 13 | url: jdbc:log4jdbc:mysql://dbhost:3306/spring_boot_db?useUnicode=true&characterEncoding=utf8&useSSL=false 14 | username: dbuser 15 | # 数据库密码含有特殊字符 '#' 处理方式 16 | # https://stackoverflow.com/questions/50672043/inside-spring-boot-application-yml-how-to-use-special-character 17 | password: dbpassword 18 | hikari: 19 | maximum-pool-size: 30 20 | minimum-idle: 20 21 | connection-timeout: 10000 22 | idle-timeout: 300000 23 | max-lifetime: 900000 24 | # =============================== 25 | # = JPA / HIBERNATE 26 | # =============================== 27 | jpa: 28 | show-sql: true 29 | hibernate: 30 | ddl-auto: none 31 | properties: 32 | hibernate: 33 | dialect: org.hibernate.spatial.dialect.mysql.MySQL5InnoDBSpatialDialect 34 | 35 | #是否需要文件服务器 36 | upload: 37 | ftp: 38 | host: 127.0.0.1 39 | port: 21 40 | dir: /opt/upload/work 41 | username: username 42 | password: password 43 | local: 44 | dir: /Users/neo/study -------------------------------------------------------------------------------- /src/main/resources/application-prod.example.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8099 3 | 4 | # =============================== 5 | # = DATA SOURCE 6 | # =============================== 7 | 8 | spring: 9 | datasource: 10 | driver-class-name: com.mysql.jdbc.Driver 11 | type: com.zaxxer.hikari.HikariDataSource 12 | url: jdbc:mysql://dbhost:3306/spring_boot_db?useUnicode=true&characterEncoding=utf8&useSSL=false 13 | username: dbuser 14 | # 数据库密码含有特殊字符 '#' 处理方式 15 | # https://stackoverflow.com/questions/50672043/inside-spring-boot-application-yml-how-to-use-special-character 16 | password: dbpassword 17 | hikari: 18 | maximum-pool-size: 30 19 | minimum-idle: 20 20 | connection-timeout: 10000 21 | idle-timeout: 300000 22 | max-lifetime: 900000 23 | # =============================== 24 | # = JPA / HIBERNATE 25 | # =============================== 26 | jpa: 27 | show-sql: true 28 | hibernate: 29 | ddl-auto: none 30 | properties: 31 | hibernate: 32 | dialect: org.hibernate.spatial.dialect.mysql.MySQL5InnoDBSpatialDialect 33 | 34 | #是否需要文件服务器 35 | upload: 36 | ftp: 37 | host: 127.0.0.1 38 | port: 21 39 | dir: /opt/upload/work 40 | username: username 41 | password: password 42 | local: 43 | dir: /Users/neo/study -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: dev 4 | application: 5 | name: web 6 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/java/com/luban/demo/LubanApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.luban.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class LubanApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------