From 9cc13dc073b0c30934530f3d2db356d42a9d763b Mon Sep 17 00:00:00 2001 From: zhangxiang Date: Tue, 14 Feb 2023 23:16:54 +0800 Subject: [PATCH] the update for code test by zhangxiang --- .gitignore | 38 + README.md | 83 +- docker/mysql/.env | 3 + docker/mysql/docker-compose.yml | 15 + docker/redis/config/redis.conf | 1 + docker/redis/docker-compose.yml | 12 + pom.xml | 130 +++ src/main/java/com/w/t/WApplication.java | 11 + .../core/constant/LocalStorageConstant.java | 13 + .../common/core/exception/BaseException.java | 45 + .../exception/GlobalExceptionHandler.java | 35 + .../common/core/exception/UtilException.java | 26 + .../java/com/w/t/common/core/response/R.java | 54 + .../com/w/t/common/core/text/CharsetKit.java | 87 ++ .../com/w/t/common/core/text/Convert.java | 1000 +++++++++++++++++ .../w/t/common/core/text/StrFormatter.java | 77 ++ .../java/com/w/t/common/core/text/UUID.java | 485 ++++++++ .../com/w/t/common/core/utils/BeanUtils.java | 110 ++ .../com/w/t/common/core/utils/DateUtils.java | 155 +++ .../w/t/common/core/utils/ExceptionUtil.java | 42 + .../com/w/t/common/core/utils/IdUtils.java | 52 + .../w/t/common/core/utils/SpringUtils.java | 114 ++ .../common/core/utils/file/FileTypeUtils.java | 47 + .../w/t/common/core/utils/file/FileUtils.java | 232 ++++ .../common/core/utils/file/MimeTypeUtils.java | 59 + .../com/w/t/common/core/web/AjaxResult.java | 154 +++ .../w/t/common/core/web/BaseController.java | 15 + .../com/w/t/common/core/web/BaseEntity.java | 23 + .../w/t/common/mybatisplus/MapperConfig.java | 7 + .../mybatisplus/MyMetaObjectHandler.java | 23 + .../redis/FastJson2JsonRedisSerializer.java | 46 + .../com/w/t/common/redis/RedisConfig.java | 38 + .../com/w/t/common/redis/RedisManager.java | 227 ++++ .../com/w/t/common/security/JwtRealm.java | 32 + .../java/com/w/t/common/security/JwtUtil.java | 49 + .../com/w/t/common/security/ShiroConfig.java | 71 ++ .../com/w/t/common/security/UserDetail.java | 39 + .../com/w/t/common/swagger/SwaggerConfig.java | 40 + .../common/util/storage/LocalStorageUtil.java | 39 + .../w/t/module/controller/AuthController.java | 43 + .../t/module/controller/FollowController.java | 49 + .../w/t/module/controller/UserController.java | 67 ++ .../java/com/w/t/module/entity/Follow.java | 20 + src/main/java/com/w/t/module/entity/User.java | 34 + .../com/w/t/module/entity/dto/LoginBody.java | 14 + .../w/t/module/entity/dto/RegisterBody.java | 12 + .../com/w/t/module/entity/dto/UserDTO.java | 20 + .../com/w/t/module/mapper/FollowMapper.java | 16 + .../com/w/t/module/mapper/UserMapper.java | 14 + .../w/t/module/service/IFollowService.java | 15 + .../com/w/t/module/service/IUserService.java | 70 ++ .../t/module/service/impl/FollowService.java | 89 ++ .../w/t/module/service/impl/UserService.java | 144 +++ .../com/w/t/module/util/ConstantUtil.java | 4 + .../com/w/t/module/util/PasswordUtil.java | 15 + src/main/resources/application-dev.yml | 20 + src/main/resources/application.yml | 20 + .../db/migration/V1.0.1__init_table.sql | 37 + src/main/resources/spy.properties | 21 + .../w/t/module/service/FollowServiceTest.java | 29 + .../w/t/module/service/UserServiceTest.java | 55 + 61 files changed, 4467 insertions(+), 70 deletions(-) create mode 100644 .gitignore create mode 100644 docker/mysql/.env create mode 100644 docker/mysql/docker-compose.yml create mode 100644 docker/redis/config/redis.conf create mode 100644 docker/redis/docker-compose.yml create mode 100644 pom.xml create mode 100644 src/main/java/com/w/t/WApplication.java create mode 100644 src/main/java/com/w/t/common/core/constant/LocalStorageConstant.java create mode 100644 src/main/java/com/w/t/common/core/exception/BaseException.java create mode 100644 src/main/java/com/w/t/common/core/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/w/t/common/core/exception/UtilException.java create mode 100644 src/main/java/com/w/t/common/core/response/R.java create mode 100644 src/main/java/com/w/t/common/core/text/CharsetKit.java create mode 100644 src/main/java/com/w/t/common/core/text/Convert.java create mode 100644 src/main/java/com/w/t/common/core/text/StrFormatter.java create mode 100644 src/main/java/com/w/t/common/core/text/UUID.java create mode 100644 src/main/java/com/w/t/common/core/utils/BeanUtils.java create mode 100644 src/main/java/com/w/t/common/core/utils/DateUtils.java create mode 100644 src/main/java/com/w/t/common/core/utils/ExceptionUtil.java create mode 100644 src/main/java/com/w/t/common/core/utils/IdUtils.java create mode 100644 src/main/java/com/w/t/common/core/utils/SpringUtils.java create mode 100644 src/main/java/com/w/t/common/core/utils/file/FileTypeUtils.java create mode 100644 src/main/java/com/w/t/common/core/utils/file/FileUtils.java create mode 100644 src/main/java/com/w/t/common/core/utils/file/MimeTypeUtils.java create mode 100644 src/main/java/com/w/t/common/core/web/AjaxResult.java create mode 100644 src/main/java/com/w/t/common/core/web/BaseController.java create mode 100644 src/main/java/com/w/t/common/core/web/BaseEntity.java create mode 100644 src/main/java/com/w/t/common/mybatisplus/MapperConfig.java create mode 100644 src/main/java/com/w/t/common/mybatisplus/MyMetaObjectHandler.java create mode 100644 src/main/java/com/w/t/common/redis/FastJson2JsonRedisSerializer.java create mode 100644 src/main/java/com/w/t/common/redis/RedisConfig.java create mode 100644 src/main/java/com/w/t/common/redis/RedisManager.java create mode 100644 src/main/java/com/w/t/common/security/JwtRealm.java create mode 100644 src/main/java/com/w/t/common/security/JwtUtil.java create mode 100644 src/main/java/com/w/t/common/security/ShiroConfig.java create mode 100644 src/main/java/com/w/t/common/security/UserDetail.java create mode 100644 src/main/java/com/w/t/common/swagger/SwaggerConfig.java create mode 100644 src/main/java/com/w/t/common/util/storage/LocalStorageUtil.java create mode 100644 src/main/java/com/w/t/module/controller/AuthController.java create mode 100644 src/main/java/com/w/t/module/controller/FollowController.java create mode 100644 src/main/java/com/w/t/module/controller/UserController.java create mode 100644 src/main/java/com/w/t/module/entity/Follow.java create mode 100644 src/main/java/com/w/t/module/entity/User.java create mode 100644 src/main/java/com/w/t/module/entity/dto/LoginBody.java create mode 100644 src/main/java/com/w/t/module/entity/dto/RegisterBody.java create mode 100644 src/main/java/com/w/t/module/entity/dto/UserDTO.java create mode 100644 src/main/java/com/w/t/module/mapper/FollowMapper.java create mode 100644 src/main/java/com/w/t/module/mapper/UserMapper.java create mode 100644 src/main/java/com/w/t/module/service/IFollowService.java create mode 100644 src/main/java/com/w/t/module/service/IUserService.java create mode 100644 src/main/java/com/w/t/module/service/impl/FollowService.java create mode 100644 src/main/java/com/w/t/module/service/impl/UserService.java create mode 100644 src/main/java/com/w/t/module/util/ConstantUtil.java create mode 100644 src/main/java/com/w/t/module/util/PasswordUtil.java create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/db/migration/V1.0.1__init_table.sql create mode 100644 src/main/resources/spy.properties create mode 100644 src/test/java/com/w/t/module/service/FollowServiceTest.java create mode 100644 src/test/java/com/w/t/module/service/UserServiceTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..064fc6b --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +docker/data + +target + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/README.md b/README.md index 447f14a..441246e 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,16 @@ -# Wiredcraft Back-end Developer Test - -Make sure you read the whole document carefully and follow the guidelines in it. - -## Context - -Build a RESTful API that can `get/create/update/delete` user data from a persistence database - -### User Model +## User Guide +##### 1. jump to target folder from project root, and build mysql container. ``` -{ - "id": "xxx", // user ID - "name": "test", // user name - "dob": "", // date of birth - "address": "", // user address - "description": "", // user description - "createdAt": "" // user created date -} +$: cd ./docker/mysql +$: docker-compose up -d ``` - -## Requirements - -### Functionality - -- The API should follow typical RESTful API design pattern. -- The data should be saved in the DB. -- Provide proper unit test. -- Provide proper API document. - -### Tech stack - -- Use Java and any framework. -- Use any DB. - -### Bonus - -- Write clear documentation on how it's designed and how to run the code. -- Write good in-code comments. -- Write good commit messages. -- An online demo is always welcome. - -### Advanced requirements - -*These are used for some further challenges. You can safely skip them if you are not asked to do any, but feel free to try out.* - -- Provide a complete user auth (authentication/authorization/etc.) strategy, such as OAuth. This should provide a way to allow end users to securely login, autenticate requests and only access their own information. -- Provide a complete logging (when/how/etc.) strategy. -- Imagine we have a new requirement right now that the user instances need to link to each other, i.e., a list of "followers/following" or "friends". Can you find out how you would design the model structure and what API you would build for querying or modifying it? -- Related to the requirement above, suppose the address of user now includes a geographic coordinate(i.e., latitude and longitude), can you build an API that, - - given a user name - - return the nearby friends - - -## What We Care About - -Feel free to use any open-source library as you see fit, but remember that we are evaluating your coding skills and problem solving skills. - -Here's what you should aim for: - -- Good use of current Java & API design best practices. -- Good testing approach. -- Extensible code. - -## FAQ - -> Where should I send back the result when I'm done? - -Fork this repo and send us a pull request when you think it's ready for review. You don't have to finish everything prior and you can continue to work on it. We don't have a deadline for the task. - -> What if I have a question? - -Feel free to make your own assumptions about the scope of this task but try to document those. You can also reach to us for questions. +##### 2. jump to target folder from project root, and build redis container. +``` +$: cd ./docker/redis +$: docker-compose up -d +``` +##### 3. run project successfully. view all api list by swagger. +``` +http://127.0.0.1/swagger-ui/index.html#/ +``` \ No newline at end of file diff --git a/docker/mysql/.env b/docker/mysql/.env new file mode 100644 index 0000000..1a433cc --- /dev/null +++ b/docker/mysql/.env @@ -0,0 +1,3 @@ +MYSQL_ROOT_PASSWORD=root +MYSQL_ROOT_HOST=% +MYSQL_DATABASE=wiredcraft \ No newline at end of file diff --git a/docker/mysql/docker-compose.yml b/docker/mysql/docker-compose.yml new file mode 100644 index 0000000..1147391 --- /dev/null +++ b/docker/mysql/docker-compose.yml @@ -0,0 +1,15 @@ +version: "3" +services: + mysql: + image: mysql:latest + container_name: mysql + restart: always + ports: + - 3306:3306 + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_ROOT_HOST: ${MYSQL_ROOT_HOST} + MYSQL_DATABASE: ${MYSQL_DATABASE} + volumes: + - ./data/mysql:/var/lib/mysql + - ./data/conf/my.cnf:/etc/my.cnf \ No newline at end of file diff --git a/docker/redis/config/redis.conf b/docker/redis/config/redis.conf new file mode 100644 index 0000000..730ec7d --- /dev/null +++ b/docker/redis/config/redis.conf @@ -0,0 +1 @@ +requirepass H3yuncom \ No newline at end of file diff --git a/docker/redis/docker-compose.yml b/docker/redis/docker-compose.yml new file mode 100644 index 0000000..f1caaea --- /dev/null +++ b/docker/redis/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3' +services: + redis: + image: redis:4.0.8-alpine + container_name: db-redis + restart: always + ports: + - 6379:6379 + volumes: + - ./config:/docker/config + - ./data:/data + command: redis-server /docker/config/redis.conf \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ffc93a6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,130 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + com.w.t + test-backend-java + 1.0.0-SNAPSHOT + + + 1.8 + 3.4.1 + 1.2.68 + 5.2.4 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-data-redis + + + com.alibaba.fastjson2 + fastjson2 + 2.0.7 + + + org.projectlombok + lombok + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + com.alibaba + fastjson + ${alibaba.fastjson.version} + + + org.flywaydb + flyway-core + ${flywaydb.flyway-core.version} + + + mysql + mysql-connector-java + 8.0.22 + + + p6spy + p6spy + 3.8.7 + + + com.h2database + h2 + 1.4.200 + + + com.google.zxing + core + 3.3.0 + + + junit + junit + test + + + org.apache.shiro + shiro-spring + 1.7.0 + + + com.auth0 + java-jwt + 3.12.0 + + + io.springfox + springfox-boot-starter + 3.0.0 + + + org.apache.commons + commons-lang3 + 3.11 + + + org.springframework.boot + spring-boot-starter-validation + + + commons-io + commons-io + 2.5 + + + commons-fileupload + commons-fileupload + 1.3.3 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/main/java/com/w/t/WApplication.java b/src/main/java/com/w/t/WApplication.java new file mode 100644 index 0000000..a774539 --- /dev/null +++ b/src/main/java/com/w/t/WApplication.java @@ -0,0 +1,11 @@ +package com.w.t; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class WApplication { + public static void main(String[] args) { + SpringApplication.run(WApplication.class, args); + } +} diff --git a/src/main/java/com/w/t/common/core/constant/LocalStorageConstant.java b/src/main/java/com/w/t/common/core/constant/LocalStorageConstant.java new file mode 100644 index 0000000..6986d8b --- /dev/null +++ b/src/main/java/com/w/t/common/core/constant/LocalStorageConstant.java @@ -0,0 +1,13 @@ +package com.w.t.common.core.constant; + +public class LocalStorageConstant { + + public static String USER_SALT="RL7fWXO@yjGZqBl(G"; + + public static String USER_DETAIL="userDetail"; + + public static String TOKEN_KEY="userDetail"; + + public static String CURRENT_USER="currentUser"; + +} diff --git a/src/main/java/com/w/t/common/core/exception/BaseException.java b/src/main/java/com/w/t/common/core/exception/BaseException.java new file mode 100644 index 0000000..880f6d1 --- /dev/null +++ b/src/main/java/com/w/t/common/core/exception/BaseException.java @@ -0,0 +1,45 @@ +package com.w.t.common.core.exception; + +import lombok.Data; + +@Data +public class BaseException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private String module; + + private String code; + + private Object[] args; + + private String defaultMessage; + + public BaseException(String module, String code, Object[] args, String defaultMessage) { + this.module = module; + this.code = code; + this.args = args; + this.defaultMessage = defaultMessage; + } + + public BaseException(String module, String code, Object[] args) { + + this(module, code, args, null); + + } + + public BaseException(String module, String defaultMessage) { + + this(module, null, null, defaultMessage); + + } + + public BaseException(String code, Object[] args) { + this(null, code, args, null); + } + + public BaseException(String defaultMessage) { + this(null, null, null, defaultMessage); + } + +} diff --git a/src/main/java/com/w/t/common/core/exception/GlobalExceptionHandler.java b/src/main/java/com/w/t/common/core/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..6572314 --- /dev/null +++ b/src/main/java/com/w/t/common/core/exception/GlobalExceptionHandler.java @@ -0,0 +1,35 @@ +package com.w.t.common.core.exception; + +import com.w.t.common.core.web.AjaxResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + @ExceptionHandler(BaseException.class) + public AjaxResult baseException(BaseException e) { + return AjaxResult.error(e.getDefaultMessage()); + } + + @ExceptionHandler(Exception.class) + public AjaxResult handleException(Exception e) + { + log.error(e.getMessage(), e); + return AjaxResult.error(e.getMessage()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object validExceptionHandler(MethodArgumentNotValidException e) + { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return AjaxResult.error(message); + } + +} diff --git a/src/main/java/com/w/t/common/core/exception/UtilException.java b/src/main/java/com/w/t/common/core/exception/UtilException.java new file mode 100644 index 0000000..6ea37bb --- /dev/null +++ b/src/main/java/com/w/t/common/core/exception/UtilException.java @@ -0,0 +1,26 @@ +package com.w.t.common.core.exception; + +/** + * 工具类异常 + * + * @author ruoyi + */ +public class UtilException extends RuntimeException +{ + private static final long serialVersionUID = 8247610319171014183L; + + public UtilException(Throwable e) + { + super(e.getMessage(), e); + } + + public UtilException(String message) + { + super(message); + } + + public UtilException(String message, Throwable throwable) + { + super(message, throwable); + } +} diff --git a/src/main/java/com/w/t/common/core/response/R.java b/src/main/java/com/w/t/common/core/response/R.java new file mode 100644 index 0000000..0248707 --- /dev/null +++ b/src/main/java/com/w/t/common/core/response/R.java @@ -0,0 +1,54 @@ +package com.w.t.common.core.response; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class R implements Serializable { + + private static final long serialVersionUID = 1l; + + public static final int SUCCESS =200; + + public static final int FAIL = 500; + + private int code; + + private String msg; + + private T data; + + private static R restResult(T data, int code, String msg) { + R apiResult = new R(); + apiResult.setCode(code); + apiResult.setData(data); + apiResult.setMsg(msg); + return apiResult; + } + + public static R success() { + return restResult("", SUCCESS, ""); + } + + public static R success(T data) { + return restResult(data, SUCCESS, ""); + } + + public static R success(T data, String msg) { + return restResult(data, SUCCESS, msg); + } + + public static R fail() { + return restResult("", FAIL, ""); + } + + public static R fail(T data) { + return restResult(data, FAIL, null); + } + + public static R fail(T data, String msg) { + return restResult(data, FAIL, msg); + } + +} diff --git a/src/main/java/com/w/t/common/core/text/CharsetKit.java b/src/main/java/com/w/t/common/core/text/CharsetKit.java new file mode 100644 index 0000000..f82c011 --- /dev/null +++ b/src/main/java/com/w/t/common/core/text/CharsetKit.java @@ -0,0 +1,87 @@ +package com.w.t.common.core.text; + +import org.apache.commons.lang3.StringUtils; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * 字符集工具类 + * + * @author ruoyi + */ +public class CharsetKit +{ + /** ISO-8859-1 */ + public static final String ISO_8859_1 = "ISO-8859-1"; + /** UTF-8 */ + public static final String UTF_8 = "UTF-8"; + /** GBK */ + public static final String GBK = "GBK"; + + /** ISO-8859-1 */ + public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1); + /** UTF-8 */ + public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8); + /** GBK */ + public static final Charset CHARSET_GBK = Charset.forName(GBK); + + /** + * 转换为Charset对象 + * + * @param charset 字符集,为空则返回默认字符集 + * @return Charset + */ + public static Charset charset(String charset) + { + return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, String srcCharset, String destCharset) + { + return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, Charset srcCharset, Charset destCharset) + { + if (null == srcCharset) + { + srcCharset = StandardCharsets.ISO_8859_1; + } + + if (null == destCharset) + { + destCharset = StandardCharsets.UTF_8; + } + + if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) + { + return source; + } + return new String(source.getBytes(srcCharset), destCharset); + } + + /** + * @return 系统字符集编码 + */ + public static String systemCharset() + { + return Charset.defaultCharset().name(); + } +} diff --git a/src/main/java/com/w/t/common/core/text/Convert.java b/src/main/java/com/w/t/common/core/text/Convert.java new file mode 100644 index 0000000..0dc0b97 --- /dev/null +++ b/src/main/java/com/w/t/common/core/text/Convert.java @@ -0,0 +1,1000 @@ +package com.w.t.common.core.text; + +import org.apache.commons.lang3.StringUtils; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.text.NumberFormat; +import java.util.Set; + +/** + * 类型转换器 + * + * @author ruoyi + */ +public class Convert +{ + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static String toStr(Object value, String defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof String) + { + return (String) value; + } + return value.toString(); + } + + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static String toStr(Object value) + { + return toStr(value, null); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Character toChar(Object value, Character defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof Character) + { + return (Character) value; + } + + final String valueStr = toStr(value, null); + return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Character toChar(Object value) + { + return toChar(value, null); + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Byte toByte(Object value, Byte defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Byte) + { + return (Byte) value; + } + if (value instanceof Number) + { + return ((Number) value).byteValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Byte.parseByte(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Byte toByte(Object value) + { + return toByte(value, null); + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Short toShort(Object value, Short defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Short) + { + return (Short) value; + } + if (value instanceof Number) + { + return ((Number) value).shortValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Short.parseShort(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Short toShort(Object value) + { + return toShort(value, null); + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Number toNumber(Object value, Number defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Number) + { + return (Number) value; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return NumberFormat.getInstance().parse(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Number toNumber(Object value) + { + return toNumber(value, null); + } + + /** + * 转换为int
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Integer toInt(Object value, Integer defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Integer) + { + return (Integer) value; + } + if (value instanceof Number) + { + return ((Number) value).intValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Integer.parseInt(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为int
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Integer toInt(Object value) + { + return toInt(value, null); + } + + /** + * 转换为Integer数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String str) + { + return toIntArray(",", str); + } + + /** + * 转换为Long数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String str) + { + return toLongArray(",", str); + } + + /** + * 转换为Integer数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Integer[] {}; + } + String[] arr = str.split(split); + final Integer[] ints = new Integer[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Integer v = toInt(arr[i], 0); + ints[i] = v; + } + return ints; + } + + /** + * 转换为Long数组
+ * + * @param split 分隔符 + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Long[] {}; + } + String[] arr = str.split(split); + final Long[] longs = new Long[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Long v = toLong(arr[i], null); + longs[i] = v; + } + return longs; + } + + /** + * 转换为String数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String str) + { + return toStrArray(",", str); + } + + /** + * 转换为String数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String split, String str) + { + return str.split(split); + } + + /** + * 转换为long
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Long toLong(Object value, Long defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Long) + { + return (Long) value; + } + if (value instanceof Number) + { + return ((Number) value).longValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).longValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为long
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Long toLong(Object value) + { + return toLong(value, null); + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Double toDouble(Object value, Double defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Double) + { + return (Double) value; + } + if (value instanceof Number) + { + return ((Number) value).doubleValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).doubleValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Double toDouble(Object value) + { + return toDouble(value, null); + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Float toFloat(Object value, Float defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Float) + { + return (Float) value; + } + if (value instanceof Number) + { + return ((Number) value).floatValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Float.parseFloat(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Float toFloat(Object value) + { + return toFloat(value, null); + } + + /** + * 转换为boolean
+ * String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Boolean toBool(Object value, Boolean defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Boolean) + { + return (Boolean) value; + } + String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + valueStr = valueStr.trim().toLowerCase(); + switch (valueStr) + { + case "true": + return true; + case "false": + return false; + case "yes": + return true; + case "ok": + return true; + case "no": + return false; + case "1": + return true; + case "0": + return false; + default: + return defaultValue; + } + } + + /** + * 转换为boolean
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Boolean toBool(Object value) + { + return toBool(value, null); + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * + * @param clazz Enum的Class + * @param value 值 + * @param defaultValue 默认值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value, E defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (clazz.isAssignableFrom(value.getClass())) + { + @SuppressWarnings("unchecked") + E myE = (E) value; + return myE; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Enum.valueOf(clazz, valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * + * @param clazz Enum的Class + * @param value 值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value) + { + return toEnum(clazz, value, null); + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value, BigInteger defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigInteger) + { + return (BigInteger) value; + } + if (value instanceof Long) + { + return BigInteger.valueOf((Long) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigInteger(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value) + { + return toBigInteger(value, null); + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigDecimal) + { + return (BigDecimal) value; + } + if (value instanceof Long) + { + return new BigDecimal((Long) value); + } + if (value instanceof Double) + { + return new BigDecimal((Double) value); + } + if (value instanceof Integer) + { + return new BigDecimal((Integer) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigDecimal(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value) + { + return toBigDecimal(value, null); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @return 字符串 + */ + public static String utf8Str(Object obj) + { + return str(obj, CharsetKit.CHARSET_UTF_8); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charsetName 字符集 + * @return 字符串 + */ + public static String str(Object obj, String charsetName) + { + return str(obj, Charset.forName(charsetName)); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(Object obj, Charset charset) + { + if (null == obj) + { + return null; + } + + if (obj instanceof String) + { + return (String) obj; + } + else if (obj instanceof byte[] || obj instanceof Byte[]) + { + return str((Byte[]) obj, charset); + } + else if (obj instanceof ByteBuffer) + { + return str((ByteBuffer) obj, charset); + } + return obj.toString(); + } + + /** + * 将byte数组转为字符串 + * + * @param bytes byte数组 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(byte[] bytes, String charset) + { + return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset)); + } + + /** + * 解码字节码 + * + * @param data 字符串 + * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 + * @return 解码后的字符串 + */ + public static String str(byte[] data, Charset charset) + { + if (data == null) + { + return null; + } + + if (null == charset) + { + return new String(data); + } + return new String(data, charset); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, String charset) + { + if (data == null) + { + return null; + } + + return str(data, Charset.forName(charset)); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, Charset charset) + { + if (null == charset) + { + charset = Charset.defaultCharset(); + } + return charset.decode(data).toString(); + } + + // ----------------------------------------------------------------------- 全角半角转换 + /** + * 半角转全角 + * + * @param input String. + * @return 全角字符串. + */ + public static String toSBC(String input) + { + return toSBC(input, null); + } + + /** + * 半角转全角 + * + * @param input String + * @param notConvertSet 不替换的字符集合 + * @return 全角字符串. + */ + public static String toSBC(String input, Set notConvertSet) + { + char c[] = input.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == ' ') + { + c[i] = '\u3000'; + } + else if (c[i] < '\177') + { + c[i] = (char) (c[i] + 65248); + + } + } + return new String(c); + } + + /** + * 全角转半角 + * + * @param input String. + * @return 半角字符串 + */ + public static String toDBC(String input) + { + return toDBC(input, null); + } + + /** + * 替换全角为半角 + * + * @param text 文本 + * @param notConvertSet 不替换的字符集合 + * @return 替换后的字符 + */ + public static String toDBC(String text, Set notConvertSet) + { + char c[] = text.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == '\u3000') + { + c[i] = ' '; + } + else if (c[i] > '\uFF00' && c[i] < '\uFF5F') + { + c[i] = (char) (c[i] - 65248); + } + } + String returnString = new String(c); + + return returnString; + } + + /** + * 数字金额大写转换 先写个完整的然后将如零拾替换成零 + * + * @param n 数字 + * @return 中文大写数字 + */ + public static String digitUppercase(double n) + { + String[] fraction = { "角", "分" }; + String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" }; + String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } }; + + String head = n < 0 ? "负" : ""; + n = Math.abs(n); + + String s = ""; + for (int i = 0; i < fraction.length; i++) + { + s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); + } + if (s.length() < 1) + { + s = "整"; + } + int integerPart = (int) Math.floor(n); + + for (int i = 0; i < unit[0].length && integerPart > 0; i++) + { + String p = ""; + for (int j = 0; j < unit[1].length && n > 0; j++) + { + p = digit[integerPart % 10] + unit[1][j] + p; + integerPart = integerPart / 10; + } + s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s; + } + return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整"); + } +} diff --git a/src/main/java/com/w/t/common/core/text/StrFormatter.java b/src/main/java/com/w/t/common/core/text/StrFormatter.java new file mode 100644 index 0000000..a3c554b --- /dev/null +++ b/src/main/java/com/w/t/common/core/text/StrFormatter.java @@ -0,0 +1,77 @@ +package com.w.t.common.core.text; + + +import org.apache.commons.lang3.StringUtils; + +/** + * 字符串格式化 + * + * @author ruoyi + */ +public class StrFormatter { + public static final String EMPTY_JSON = "{}"; + public static final char C_BACKSLASH = '\\'; + public static final char C_DELIM_START = '{'; + public static final char C_DELIM_END = '}'; + + /** + * 格式化字符串
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param strPattern 字符串模板 + * @param argArray 参数列表 + * @return 结果 + */ + public static String format(final String strPattern, final Object... argArray) { + if (StringUtils.isEmpty(strPattern) || null == argArray) { + return strPattern; + } + final int strPatternLength = strPattern.length(); + + // 初始化定义好的长度以获得更好的性能 + StringBuilder sbuf = new StringBuilder(strPatternLength + 50); + + int handledPosition = 0; + int delimIndex;// 占位符所在位置 + for (int argIndex = 0; argIndex < argArray.length; argIndex++) { + delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition); + if (delimIndex == -1) { + if (handledPosition == 0) { + return strPattern; + } else { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 + sbuf.append(strPattern, handledPosition, strPatternLength); + return sbuf.toString(); + } + } else { + if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) { + if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) { + // 转义符之前还有一个转义符,占位符依旧有效 + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } else { + // 占位符被转义 + argIndex--; + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(C_DELIM_START); + handledPosition = delimIndex + 1; + } + } else { + // 正常占位符 + sbuf.append(strPattern, handledPosition, delimIndex); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + } + } + // 加入最后一个占位符后所有的字符 + sbuf.append(strPattern, handledPosition, strPattern.length()); + + return sbuf.toString(); + } +} diff --git a/src/main/java/com/w/t/common/core/text/UUID.java b/src/main/java/com/w/t/common/core/text/UUID.java new file mode 100644 index 0000000..a9e0e3f --- /dev/null +++ b/src/main/java/com/w/t/common/core/text/UUID.java @@ -0,0 +1,485 @@ +package com.w.t.common.core.text; + +import com.w.t.common.core.exception.UtilException; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 提供通用唯一识别码(universally unique identifier)(UUID)实现 + * + * @author ruoyi + */ +public final class UUID implements java.io.Serializable, Comparable +{ + private static final long serialVersionUID = -1185015143654744140L; + + /** + * SecureRandom 的单例 + * + */ + private static class Holder + { + static final SecureRandom numberGenerator = getSecureRandom(); + } + + /** 此UUID的最高64有效位 */ + private final long mostSigBits; + + /** 此UUID的最低64有效位 */ + private final long leastSigBits; + + /** + * 私有构造 + * + * @param data 数据 + */ + private UUID(byte[] data) + { + long msb = 0; + long lsb = 0; + assert data.length == 16 : "data must be 16 bytes in length"; + for (int i = 0; i < 8; i++) + { + msb = (msb << 8) | (data[i] & 0xff); + } + for (int i = 8; i < 16; i++) + { + lsb = (lsb << 8) | (data[i] & 0xff); + } + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + /** + * 使用指定的数据构造新的 UUID。 + * + * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 + * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 + */ + public UUID(long mostSigBits, long leastSigBits) + { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID fastUUID() + { + return randomUUID(false); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID() + { + return randomUUID(true); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID(boolean isSecure) + { + final Random ng = isSecure ? Holder.numberGenerator : getRandom(); + + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(randomBytes); + } + + /** + * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 + * + * @param name 用于构造 UUID 的字节数组。 + * + * @return 根据指定数组生成的 {@code UUID} + */ + public static UUID nameUUIDFromBytes(byte[] name) + { + MessageDigest md; + try + { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException nsae) + { + throw new InternalError("MD5 not supported"); + } + byte[] md5Bytes = md.digest(name); + md5Bytes[6] &= 0x0f; /* clear version */ + md5Bytes[6] |= 0x30; /* set to version 3 */ + md5Bytes[8] &= 0x3f; /* clear variant */ + md5Bytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(md5Bytes); + } + + /** + * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 + * + * @param name 指定 {@code UUID} 字符串 + * @return 具有指定值的 {@code UUID} + * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 + * + */ + public static UUID fromString(String name) + { + String[] components = name.split("-"); + if (components.length != 5) + { + throw new IllegalArgumentException("Invalid UUID string: " + name); + } + for (int i = 0; i < 5; i++) + { + components[i] = "0x" + components[i]; + } + + long mostSigBits = Long.decode(components[0]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[2]).longValue(); + + long leastSigBits = Long.decode(components[3]).longValue(); + leastSigBits <<= 48; + leastSigBits |= Long.decode(components[4]).longValue(); + + return new UUID(mostSigBits, leastSigBits); + } + + /** + * 返回此 UUID 的 128 位值中的最低有效 64 位。 + * + * @return 此 UUID 的 128 位值中的最低有效 64 位。 + */ + public long getLeastSignificantBits() + { + return leastSigBits; + } + + /** + * 返回此 UUID 的 128 位值中的最高有效 64 位。 + * + * @return 此 UUID 的 128 位值中最高有效 64 位。 + */ + public long getMostSignificantBits() + { + return mostSigBits; + } + + /** + * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 + *

+ * 版本号具有以下含意: + *

    + *
  • 1 基于时间的 UUID + *
  • 2 DCE 安全 UUID + *
  • 3 基于名称的 UUID + *
  • 4 随机生成的 UUID + *
+ * + * @return 此 {@code UUID} 的版本号 + */ + public int version() + { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

+ * 变体号具有以下含意: + *

    + *
  • 0 为 NCS 向后兼容保留 + *
  • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
  • 6 保留,微软向后兼容 + *
  • 7 保留供以后定义使用 + *
+ * + * @return 此 {@code UUID} 相关联的变体号 + */ + public int variant() + { + // This field is composed of a varying number of bits. + // 0 - - Reserved for NCS backward compatibility + // 1 0 - The IETF aka Leach-Salz variant (used by this class) + // 1 1 0 Reserved, Microsoft backward compatibility + // 1 1 1 Reserved for future definition. + return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); + } + + /** + * 与此 UUID 相关联的时间戳值。 + * + *

+ * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
+ * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

+ * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 + */ + public long timestamp() throws UnsupportedOperationException + { + checkTimeBase(); + return (mostSigBits & 0x0FFFL) << 48// + | ((mostSigBits >> 16) & 0x0FFFFL) << 32// + | mostSigBits >>> 32; + } + + /** + * 与此 UUID 相关联的时钟序列值。 + * + *

+ * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

+ * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 + * UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的时钟序列 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public int clockSequence() throws UnsupportedOperationException + { + checkTimeBase(); + return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); + } + + /** + * 与此 UUID 相关的节点值。 + * + *

+ * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

+ * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException + { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() + { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 + * @return 此{@code UUID} 的字符串表现形式 + */ + public String toString(boolean isSimple) + { + final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); + // time_low + builder.append(digits(mostSigBits >> 32, 8)); + if (false == isSimple) + { + builder.append('-'); + } + // time_mid + builder.append(digits(mostSigBits >> 16, 4)); + if (false == isSimple) + { + builder.append('-'); + } + // time_high_and_version + builder.append(digits(mostSigBits, 4)); + if (false == isSimple) + { + builder.append('-'); + } + // variant_and_sequence + builder.append(digits(leastSigBits >> 48, 4)); + if (false == isSimple) + { + builder.append('-'); + } + // node + builder.append(digits(leastSigBits, 12)); + + return builder.toString(); + } + + /** + * 返回此 UUID 的哈希码。 + * + * @return UUID 的哈希码值。 + */ + @Override + public int hashCode() + { + long hilo = mostSigBits ^ leastSigBits; + return ((int) (hilo >> 32)) ^ (int) hilo; + } + + /** + * 将此对象与指定对象比较。 + *

+ * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 + * + * @param obj 要与之比较的对象 + * + * @return 如果对象相同,则返回 {@code true};否则返回 {@code false} + */ + @Override + public boolean equals(Object obj) + { + if ((null == obj) || (obj.getClass() != UUID.class)) + { + return false; + } + UUID id = (UUID) obj; + return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + // Comparison Operations + + /** + * 将此 UUID 与指定的 UUID 比较。 + * + *

+ * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 + * + * @param val 与此 UUID 比较的 UUID + * + * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 + * + */ + @Override + public int compareTo(UUID val) + { + // The ordering is intentionally set up so that the UUIDs + // can simply be numerically compared as two numbers + return (this.mostSigBits < val.mostSigBits ? -1 : // + (this.mostSigBits > val.mostSigBits ? 1 : // + (this.leastSigBits < val.leastSigBits ? -1 : // + (this.leastSigBits > val.leastSigBits ? 1 : // + 0)))); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Private method start + /** + * 返回指定数字对应的hex值 + * + * @param val 值 + * @param digits 位 + * @return 值 + */ + private static String digits(long val, int digits) + { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * 检查是否为time-based版本UUID + */ + private void checkTimeBase() + { + if (version() != 1) + { + throw new UnsupportedOperationException("Not a time-based UUID"); + } + } + + /** + * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) + * + * @return {@link SecureRandom} + */ + public static SecureRandom getSecureRandom() + { + try + { + return SecureRandom.getInstance("SHA1PRNG"); + } + catch (NoSuchAlgorithmException e) + { + throw new UtilException(e); + } + } + + /** + * 获取随机数生成器对象
+ * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() + { + return ThreadLocalRandom.current(); + } +} diff --git a/src/main/java/com/w/t/common/core/utils/BeanUtils.java b/src/main/java/com/w/t/common/core/utils/BeanUtils.java new file mode 100644 index 0000000..04269da --- /dev/null +++ b/src/main/java/com/w/t/common/core/utils/BeanUtils.java @@ -0,0 +1,110 @@ +package com.w.t.common.core.utils; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Bean 工具类 + * + * @author ruoyi + */ +public class BeanUtils extends org.springframework.beans.BeanUtils +{ + /** Bean方法名中属性名开始的下标 */ + private static final int BEAN_METHOD_PROP_INDEX = 3; + + /** * 匹配getter方法的正则表达式 */ + private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)"); + + /** * 匹配setter方法的正则表达式 */ + private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)"); + + /** + * Bean属性复制工具方法。 + * + * @param dest 目标对象 + * @param src 源对象 + */ + public static void copyBeanProp(Object dest, Object src) + { + try + { + copyProperties(src, dest); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * 获取对象的setter方法。 + * + * @param obj 对象 + * @return 对象的setter方法列表 + */ + public static List getSetterMethods(Object obj) + { + // setter方法列表 + List setterMethods = new ArrayList(); + + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + + // 查找setter方法 + + for (Method method : methods) + { + Matcher m = SET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 1)) + { + setterMethods.add(method); + } + } + // 返回setter方法列表 + return setterMethods; + } + + /** + * 获取对象的getter方法。 + * + * @param obj 对象 + * @return 对象的getter方法列表 + */ + + public static List getGetterMethods(Object obj) + { + // getter方法列表 + List getterMethods = new ArrayList(); + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + // 查找getter方法 + for (Method method : methods) + { + Matcher m = GET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 0)) + { + getterMethods.add(method); + } + } + // 返回getter方法列表 + return getterMethods; + } + + /** + * 检查Bean方法名中的属性名是否相等。
+ * 如getName()和setName()属性名一样,getName()和setAge()属性名不一样。 + * + * @param m1 方法名1 + * @param m2 方法名2 + * @return 属性名一样返回true,否则返回false + */ + + public static boolean isMethodPropEquals(String m1, String m2) + { + return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX)); + } +} diff --git a/src/main/java/com/w/t/common/core/utils/DateUtils.java b/src/main/java/com/w/t/common/core/utils/DateUtils.java new file mode 100644 index 0000000..aeb8114 --- /dev/null +++ b/src/main/java/com/w/t/common/core/utils/DateUtils.java @@ -0,0 +1,155 @@ +package com.w.t.common.core.utils; + +import java.lang.management.ManagementFactory; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.apache.commons.lang3.time.DateFormatUtils; + +/** + * 时间工具类 + * + * @author ruoyi + */ +public class DateUtils extends org.apache.commons.lang3.time.DateUtils +{ + public static String YYYY = "yyyy"; + + public static String YYYY_MM = "yyyy-MM"; + + public static String YYYY_MM_DD = "yyyy-MM-dd"; + + public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; + + public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + private static String[] parsePatterns = { + "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", + "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", + "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; + + /** + * 获取当前Date型日期 + * + * @return Date() 当前日期 + */ + public static Date getNowDate() + { + return new Date(); + } + + /** + * 获取当前日期, 默认格式为yyyy-MM-dd + * + * @return String + */ + public static String getDate() + { + return dateTimeNow(YYYY_MM_DD); + } + + public static final String getTime() + { + return dateTimeNow(YYYY_MM_DD_HH_MM_SS); + } + + public static final String dateTimeNow() + { + return dateTimeNow(YYYYMMDDHHMMSS); + } + + public static final String dateTimeNow(final String format) + { + return parseDateToStr(format, new Date()); + } + + public static final String dateTime(final Date date) + { + return parseDateToStr(YYYY_MM_DD, date); + } + + public static final String parseDateToStr(final String format, final Date date) + { + return new SimpleDateFormat(format).format(date); + } + + public static final Date dateTime(final String format, final String ts) + { + try + { + return new SimpleDateFormat(format).parse(ts); + } + catch (ParseException e) + { + throw new RuntimeException(e); + } + } + + /** + * 日期路径 即年/月/日 如2018/08/08 + */ + public static final String datePath() + { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyy/MM/dd"); + } + + /** + * 日期路径 即年/月/日 如20180808 + */ + public static final String dateTime() + { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyyMMdd"); + } + + /** + * 日期型字符串转化为日期 格式 + */ + public static Date parseDate(Object str) + { + if (str == null) + { + return null; + } + try + { + return parseDate(str.toString(), parsePatterns); + } + catch (ParseException e) + { + return null; + } + } + + /** + * 获取服务器启动时间 + */ + public static Date getServerStartDate() + { + long time = ManagementFactory.getRuntimeMXBean().getStartTime(); + return new Date(time); + } + + /** + * 计算两个时间差 + */ + public static String getDatePoor(Date endDate, Date nowDate) + { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + // long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - nowDate.getTime(); + // 计算差多少天 + long day = diff / nd; + // 计算差多少小时 + long hour = diff % nd / nh; + // 计算差多少分钟 + long min = diff % nd % nh / nm; + // 计算差多少秒//输出结果 + // long sec = diff % nd % nh % nm / ns; + return day + "天" + hour + "小时" + min + "分钟"; + } +} diff --git a/src/main/java/com/w/t/common/core/utils/ExceptionUtil.java b/src/main/java/com/w/t/common/core/utils/ExceptionUtil.java new file mode 100644 index 0000000..512c852 --- /dev/null +++ b/src/main/java/com/w/t/common/core/utils/ExceptionUtil.java @@ -0,0 +1,42 @@ +package com.w.t.common.core.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; + +/** + * 错误信息处理类。 + * + * @author ruoyi + */ +public class ExceptionUtil +{ + /** + * 获取exception的详细错误信息。 + */ + public static String getExceptionMessage(Throwable e) + { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw, true)); + String str = sw.toString(); + return str; + } + + public static String getRootErrorMseeage(Exception e) + { + Throwable root = ExceptionUtils.getRootCause(e); + root = (root == null ? e : root); + if (root == null) + { + return ""; + } + String msg = root.getMessage(); + if (msg == null) + { + return "null"; + } + return StringUtils.defaultString(msg); + } +} diff --git a/src/main/java/com/w/t/common/core/utils/IdUtils.java b/src/main/java/com/w/t/common/core/utils/IdUtils.java new file mode 100644 index 0000000..3e95e4c --- /dev/null +++ b/src/main/java/com/w/t/common/core/utils/IdUtils.java @@ -0,0 +1,52 @@ +package com.w.t.common.core.utils; + + +import com.w.t.common.core.text.UUID; + +/** + * ID生成器工具类 + * + * @author ruoyi + */ +public class IdUtils +{ + /** + * 获取随机UUID + * + * @return 随机UUID + */ + public static String randomUUID() + { + return UUID.randomUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线 + * + * @return 简化的UUID,去掉了横线 + */ + public static String simpleUUID() + { + return UUID.randomUUID().toString(true); + } + + /** + * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 随机UUID + */ + public static String fastUUID() + { + return UUID.fastUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 简化的UUID,去掉了横线 + */ + public static String fastSimpleUUID() + { + return UUID.fastUUID().toString(true); + } +} diff --git a/src/main/java/com/w/t/common/core/utils/SpringUtils.java b/src/main/java/com/w/t/common/core/utils/SpringUtils.java new file mode 100644 index 0000000..3eccec7 --- /dev/null +++ b/src/main/java/com/w/t/common/core/utils/SpringUtils.java @@ -0,0 +1,114 @@ +package com.w.t.common.core.utils; + +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.stereotype.Component; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author ruoyi + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor +{ + /** Spring应用上下文环境 */ + private static ConfigurableListableBeanFactory beanFactory; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException + { + SpringUtils.beanFactory = beanFactory; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws BeansException + * + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws BeansException + * + */ + public static T getBean(Class clz) throws BeansException + { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) + { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + * @throws NoSuchBeanDefinitionException + * + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws NoSuchBeanDefinitionException + * + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws NoSuchBeanDefinitionException + * + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) + { + return (T) AopContext.currentProxy(); + } +} diff --git a/src/main/java/com/w/t/common/core/utils/file/FileTypeUtils.java b/src/main/java/com/w/t/common/core/utils/file/FileTypeUtils.java new file mode 100644 index 0000000..0a677d5 --- /dev/null +++ b/src/main/java/com/w/t/common/core/utils/file/FileTypeUtils.java @@ -0,0 +1,47 @@ +package com.w.t.common.core.utils.file; + +import java.io.File; +import org.apache.commons.lang3.StringUtils; + +/** + * 文件类型工具类 + * + * @author ruoyi + */ +public class FileTypeUtils +{ + /** + * 获取文件类型 + *

+ * 例如: ruoyi.txt, 返回: txt + * + * @param file 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(File file) + { + if (null == file) + { + return StringUtils.EMPTY; + } + return getFileType(file.getName()); + } + + /** + * 获取文件类型 + *

+ * 例如: ruoyi.txt, 返回: txt + * + * @param fileName 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(String fileName) + { + int separatorIndex = fileName.lastIndexOf("."); + if (separatorIndex < 0) + { + return ""; + } + return fileName.substring(separatorIndex + 1).toLowerCase(); + } +} \ No newline at end of file diff --git a/src/main/java/com/w/t/common/core/utils/file/FileUtils.java b/src/main/java/com/w/t/common/core/utils/file/FileUtils.java new file mode 100644 index 0000000..9394a9b --- /dev/null +++ b/src/main/java/com/w/t/common/core/utils/file/FileUtils.java @@ -0,0 +1,232 @@ +package com.w.t.common.core.utils.file; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +/** + * 文件处理工具类 + * + * @author ruoyi + */ +public class FileUtils extends org.apache.commons.io.FileUtils { + /** + * 字符常量:斜杠 {@code '/'} + */ + public static final char SLASH = '/'; + + /** + * 字符常量:反斜杠 {@code '\\'} + */ + public static final char BACKSLASH = '\\'; + + public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; + + public static boolean isValidFile(String filePath) { + + File file = new File(filePath); + return file.exists() && file.isFile(); + + } + + /** + * 输出指定文件的byte数组 + * + * @param filePath 文件路径 + * @param os 输出流 + * @return + */ + public static void writeBytes(String filePath, OutputStream os) throws IOException { + FileInputStream fis = null; + try { + File file = new File(filePath); + if (!file.exists()) { + throw new FileNotFoundException(filePath); + } + fis = new FileInputStream(file); + byte[] b = new byte[1024]; + int length; + while ((length = fis.read(b)) > 0) { + os.write(b, 0, length); + } + } catch (IOException e) { + throw e; + } finally { + if (os != null) { + try { + os.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + if (fis != null) { + try { + fis.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + } + + /** + * 删除文件 + * + * @param filePath 文件 + * @return + */ + public static boolean deleteFile(String filePath) { + boolean flag = false; + File file = new File(filePath); + // 路径为文件且不为空则进行删除 + if (file.isFile() && file.exists()) { + file.delete(); + flag = true; + } + return flag; + } + + /** + * 文件名称验证 + * + * @param filename 文件名称 + * @return true 正常 false 非法 + */ + public static boolean isValidFilename(String filename) { + return filename.matches(FILENAME_PATTERN); + } + + /** + * 检查文件是否可下载 + * + * @param resource 需要下载的文件 + * @return true 正常 false 非法 + */ + public static boolean checkAllowDownload(String resource) { + // 禁止目录上跳级别 + if (StringUtils.contains(resource, "..")) { + return false; + } + + // 检查允许下载的文件规则 + if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) { + return true; + } + + // 不在允许下载的文件规则 + return false; + } + + /** + * 下载文件名重新编码 + * + * @param request 请求对象 + * @param fileName 文件名 + * @return 编码后的文件名 + */ + public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException { + final String agent = request.getHeader("USER-AGENT"); + String filename = fileName; + if (agent.contains("MSIE")) { + // IE浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + filename = filename.replace("+", " "); + } else if (agent.contains("Firefox")) { + // 火狐浏览器 + filename = new String(fileName.getBytes(), "ISO8859-1"); + } else if (agent.contains("Chrome")) { + // google浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } else { + // 其它浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + return filename; + } + + /** + * 返回文件名 + * + * @param filePath 文件 + * @return 文件名 + */ + public static String getName(String filePath) { + if (null == filePath) { + return null; + } + int len = filePath.length(); + if (0 == len) { + return filePath; + } + if (isFileSeparator(filePath.charAt(len - 1))) { + // 以分隔符结尾的去掉结尾分隔符 + len--; + } + + int begin = 0; + char c; + for (int i = len - 1; i > -1; i--) { + c = filePath.charAt(i); + if (isFileSeparator(c)) { + // 查找最后一个路径分隔符(/或者\) + begin = i + 1; + break; + } + } + + return filePath.substring(begin, len); + } + + /** + * 是否为Windows或者Linux(Unix)文件分隔符
+ * Windows平台下分隔符为\,Linux(Unix)为/ + * + * @param c 字符 + * @return 是否为Windows或者Linux(Unix)文件分隔符 + */ + public static boolean isFileSeparator(char c) { + return SLASH == c || BACKSLASH == c; + } + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + * @return + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException { + String percentEncodedFileName = percentEncode(realFileName); + + StringBuilder contentDispositionValue = new StringBuilder(); + contentDispositionValue.append("attachment; filename=") + .append(percentEncodedFileName) + .append(";") + .append("filename*=") + .append("utf-8''") + .append(percentEncodedFileName); + + response.setHeader("Content-disposition", contentDispositionValue.toString()); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public static String percentEncode(String s) throws UnsupportedEncodingException { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); + return encode.replaceAll("\\+", "%20"); + } +} diff --git a/src/main/java/com/w/t/common/core/utils/file/MimeTypeUtils.java b/src/main/java/com/w/t/common/core/utils/file/MimeTypeUtils.java new file mode 100644 index 0000000..f3588fe --- /dev/null +++ b/src/main/java/com/w/t/common/core/utils/file/MimeTypeUtils.java @@ -0,0 +1,59 @@ +package com.w.t.common.core.utils.file; + +/** + * 媒体类型工具类 + * + * @author ruoyi + */ +public class MimeTypeUtils +{ + public static final String IMAGE_PNG = "image/png"; + + public static final String IMAGE_JPG = "image/jpg"; + + public static final String IMAGE_JPEG = "image/jpeg"; + + public static final String IMAGE_BMP = "image/bmp"; + + public static final String IMAGE_GIF = "image/gif"; + + public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" }; + + public static final String[] FLASH_EXTENSION = { "swf", "flv" }; + + public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", + "asf", "rm", "rmvb" }; + + public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" }; + + public static final String[] DEFAULT_ALLOWED_EXTENSION = { + // 图片 + "bmp", "gif", "jpg", "jpeg", "png", + // word excel powerpoint + "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", + // 压缩文件 + "rar", "zip", "gz", "bz2", + // 视频格式 + "mp4", "avi", "rmvb", + // pdf + "pdf" }; + + public static String getExtension(String prefix) + { + switch (prefix) + { + case IMAGE_PNG: + return "png"; + case IMAGE_JPG: + return "jpg"; + case IMAGE_JPEG: + return "jpeg"; + case IMAGE_BMP: + return "bmp"; + case IMAGE_GIF: + return "gif"; + default: + return ""; + } + } +} diff --git a/src/main/java/com/w/t/common/core/web/AjaxResult.java b/src/main/java/com/w/t/common/core/web/AjaxResult.java new file mode 100644 index 0000000..0a89464 --- /dev/null +++ b/src/main/java/com/w/t/common/core/web/AjaxResult.java @@ -0,0 +1,154 @@ +package com.w.t.common.core.web; + +import org.springframework.http.HttpStatus; +import java.util.HashMap; + +/** + * 操作消息提醒 + * + * @author ruoyi + */ +public class AjaxResult extends HashMap { + private static final long serialVersionUID = 1L; + + /** + * 状态码 + */ + public static final String CODE_TAG = "code"; + + /** + * 返回内容 + */ + public static final String MSG_TAG = "msg"; + + /** + * 数据对象 + */ + public static final String DATA_TAG = "data"; + + /** + * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 + */ + public AjaxResult() { + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public AjaxResult(int code, String msg) { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public AjaxResult(int code, String msg, Object data) { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + if (null != data) { + super.put(DATA_TAG, data); + } + } + + /** + * 方便链式调用 + * + * @param key + * @param value + * @return + */ + @Override + public AjaxResult put(String key, Object value) { + super.put(key, value); + return this; + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static AjaxResult success() { + return AjaxResult.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static AjaxResult success(Object data) { + return AjaxResult.success("操作成功", data); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @return 成功消息 + */ + public static AjaxResult success(String msg) { + return AjaxResult.success(msg, null); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 成功消息 + */ + public static AjaxResult success(String msg, Object data) { + return new AjaxResult(HttpStatus.OK.value(), msg, data); + } + + /** + * 返回错误消息 + * + * @return + */ + public static AjaxResult error() { + return AjaxResult.error("操作失败"); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult error(String msg) { + return AjaxResult.error(msg, null); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static AjaxResult error(String msg, Object data) { + return new AjaxResult(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult error(int code, String msg) { + return new AjaxResult(code, msg, null); + } +} + diff --git a/src/main/java/com/w/t/common/core/web/BaseController.java b/src/main/java/com/w/t/common/core/web/BaseController.java new file mode 100644 index 0000000..ae13f4e --- /dev/null +++ b/src/main/java/com/w/t/common/core/web/BaseController.java @@ -0,0 +1,15 @@ +package com.w.t.common.core.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaseController { + + protected final Logger logger = LoggerFactory.getLogger(BaseController.class); + + protected AjaxResult toAjax(int rows) + { + return rows > 0 ? AjaxResult.success() : AjaxResult.error(); + } + +} diff --git a/src/main/java/com/w/t/common/core/web/BaseEntity.java b/src/main/java/com/w/t/common/core/web/BaseEntity.java new file mode 100644 index 0000000..6fd94f2 --- /dev/null +++ b/src/main/java/com/w/t/common/core/web/BaseEntity.java @@ -0,0 +1,23 @@ +package com.w.t.common.core.web; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +import java.io.Serializable; +import java.sql.Timestamp; + +@Data +public class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableField(fill = FieldFill.INSERT) + private Timestamp createAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private Timestamp updateAt; + +} diff --git a/src/main/java/com/w/t/common/mybatisplus/MapperConfig.java b/src/main/java/com/w/t/common/mybatisplus/MapperConfig.java new file mode 100644 index 0000000..8cbd489 --- /dev/null +++ b/src/main/java/com/w/t/common/mybatisplus/MapperConfig.java @@ -0,0 +1,7 @@ +package com.w.t.common.mybatisplus; + +import org.mybatis.spring.annotation.MapperScan; + +@MapperScan("com.w.t.module") +public class MapperConfig { +} diff --git a/src/main/java/com/w/t/common/mybatisplus/MyMetaObjectHandler.java b/src/main/java/com/w/t/common/mybatisplus/MyMetaObjectHandler.java new file mode 100644 index 0000000..1d20040 --- /dev/null +++ b/src/main/java/com/w/t/common/mybatisplus/MyMetaObjectHandler.java @@ -0,0 +1,23 @@ +package com.w.t.common.mybatisplus; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; +import java.sql.Timestamp; + +@Component +public class MyMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + Timestamp currentTime=new Timestamp(System.currentTimeMillis()); + this.setFieldValByName("createAt", currentTime, metaObject); + } + + @Override + public void updateFill(MetaObject metaObject) { + Timestamp currentTime=new Timestamp(System.currentTimeMillis()); + this.setFieldValByName("updateAt", currentTime, metaObject); + } + +} diff --git a/src/main/java/com/w/t/common/redis/FastJson2JsonRedisSerializer.java b/src/main/java/com/w/t/common/redis/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..97240dc --- /dev/null +++ b/src/main/java/com/w/t/common/redis/FastJson2JsonRedisSerializer.java @@ -0,0 +1,46 @@ +package com.w.t.common.redis; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; + +import java.nio.charset.Charset; + + +public class FastJson2JsonRedisSerializer implements RedisSerializer +{ + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + private Class clazz; + + + public FastJson2JsonRedisSerializer(Class clazz) + { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) + { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length <= 0) + { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + + return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType); + } +} diff --git a/src/main/java/com/w/t/common/redis/RedisConfig.java b/src/main/java/com/w/t/common/redis/RedisConfig.java new file mode 100644 index 0000000..668bb00 --- /dev/null +++ b/src/main/java/com/w/t/common/redis/RedisConfig.java @@ -0,0 +1,38 @@ +package com.w.t.common.redis; + +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@EnableCaching +@AutoConfigureBefore(RedisAutoConfiguration.class) +public class RedisConfig extends CachingConfigurerSupport +{ + @Bean + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) + { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } +} diff --git a/src/main/java/com/w/t/common/redis/RedisManager.java b/src/main/java/com/w/t/common/redis/RedisManager.java new file mode 100644 index 0000000..fb64159 --- /dev/null +++ b/src/main/java/com/w/t/common/redis/RedisManager.java @@ -0,0 +1,227 @@ +package com.w.t.common.redis; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +@Component +public class RedisManager { + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public long deleteObject(final Collection collection) { + return redisTemplate.delete(collection); + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) { + return redisTemplate.keys(pattern); + } +} diff --git a/src/main/java/com/w/t/common/security/JwtRealm.java b/src/main/java/com/w/t/common/security/JwtRealm.java new file mode 100644 index 0000000..8263f33 --- /dev/null +++ b/src/main/java/com/w/t/common/security/JwtRealm.java @@ -0,0 +1,32 @@ +package com.w.t.common.security; + +import org.apache.shiro.authc.*; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import com.w.t.common.util.storage.LocalStorageUtil; + +public class JwtRealm extends AuthorizingRealm { + + @Override + public boolean supports(AuthenticationToken token) { + return token != null && token instanceof BearerToken; + } + + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { + UserDetail userDetail = JwtUtil.parseToken(token.getCredentials().toString()); + if (userDetail == null) { + return null; + } + LocalStorageUtil.setCurrentUserId(Long.valueOf(userDetail.getUserId())); + return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName()); + } + + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { + return null; + } + + +} diff --git a/src/main/java/com/w/t/common/security/JwtUtil.java b/src/main/java/com/w/t/common/security/JwtUtil.java new file mode 100644 index 0000000..0986231 --- /dev/null +++ b/src/main/java/com/w/t/common/security/JwtUtil.java @@ -0,0 +1,49 @@ +package com.w.t.common.security; + +import com.alibaba.fastjson.JSON; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.w.t.common.core.constant.LocalStorageConstant; + +import java.util.Date; + +public class JwtUtil { + + private static final String SECRET = "DuoDuo/2019/01/30"; + + private static long defaultTokenLifetime = 1000 * 60 * 60 * 24; + + public static String createToken(String tokenKey,Long userId) { + UserDetail userDetail = new UserDetail(userId); + return createToken(tokenKey,userDetail); + } + + public static String createToken(String tokenKey,UserDetail userDetail) { + //加密算法 + Algorithm algorithm = Algorithm.HMAC256(SECRET); + //发布时间 + Date publishTime = new Date(System.currentTimeMillis()); + //过期时间 + Date expiredTime = new Date(System.currentTimeMillis() + defaultTokenLifetime); + String token = JWT.create() + .withIssuedAt(publishTime) + .withExpiresAt(expiredTime) + .withClaim(LocalStorageConstant.TOKEN_KEY, tokenKey) + .withClaim(LocalStorageConstant.USER_DETAIL, JSON.toJSONString(userDetail)) + .sign(algorithm); + return token; + } + + public static UserDetail parseToken(String token) { + //加密算法 + Algorithm algorithm = Algorithm.HMAC256(SECRET); + try { + DecodedJWT decodedJWT = JWT.require(algorithm).build().verify(token); + return JSON.parseObject(decodedJWT.getClaim(LocalStorageConstant.USER_DETAIL).asString(), UserDetail.class); + } catch (Exception e) { + return null; + } + } + +} diff --git a/src/main/java/com/w/t/common/security/ShiroConfig.java b/src/main/java/com/w/t/common/security/ShiroConfig.java new file mode 100644 index 0000000..4935630 --- /dev/null +++ b/src/main/java/com/w/t/common/security/ShiroConfig.java @@ -0,0 +1,71 @@ +package com.w.t.common.security; + +import org.apache.shiro.mgt.DefaultSecurityManager; +import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; +import org.apache.shiro.mgt.DefaultSubjectDAO; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import javax.servlet.Filter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +@Configuration +public class ShiroConfig { + + @Bean + public JwtRealm shiroJwtRealm(){ + return new JwtRealm(); + } + + @Bean + public DefaultSecurityManager securityManager(JwtRealm jwtRealm){ + DefaultSecurityManager defaultSecurityManager=new DefaultWebSecurityManager(); + defaultSecurityManager.setRealm(jwtRealm); + // 关闭自带session + DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); + defaultSessionStorageEvaluator.setSessionStorageEnabled(false); + + DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); + subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); + defaultSecurityManager.setSubjectDAO(subjectDAO); + return defaultSecurityManager; + } + + @Bean + public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager defaultSecurityManager){ + ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean(); + + shiroFilterFactoryBean.setSecurityManager(defaultSecurityManager); + + Map filterMap=new LinkedHashMap<>(); + filterMap.put("bearer",new BearerHttpAuthenticationFilter()); + shiroFilterFactoryBean.setFilters(filterMap); + + Map filterRuleMap=new HashMap<>(); + + //Swagger + filterRuleMap.put("/swagger-ui.html","anon"); + filterRuleMap.put("/swagger-ui/*","anon"); + filterRuleMap.put("/swagger-resources/**","anon"); + filterRuleMap.put("/v2/api-docs","anon"); + filterRuleMap.put("/v3/api-docs","anon"); + filterRuleMap.put("/webjars/**","anon"); + + //登录 + filterRuleMap.put("/auth/login","anon"); + + //交换 + filterRuleMap.put("/e/**","anon"); + + //bearer jwt + filterRuleMap.put("/**","bearer"); + + shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap); + return shiroFilterFactoryBean; + } + +} diff --git a/src/main/java/com/w/t/common/security/UserDetail.java b/src/main/java/com/w/t/common/security/UserDetail.java new file mode 100644 index 0000000..f7471d8 --- /dev/null +++ b/src/main/java/com/w/t/common/security/UserDetail.java @@ -0,0 +1,39 @@ +package com.w.t.common.security; + +import java.util.Set; + +public class UserDetail { + + private Long userId; + + private Set authorities; + + public UserDetail() { + } + + public UserDetail(Long userId) { + this.userId = userId; + } + + public UserDetail(Long userId, Set authorities) { + this.userId = userId; + this.authorities = authorities; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + +} diff --git a/src/main/java/com/w/t/common/swagger/SwaggerConfig.java b/src/main/java/com/w/t/common/swagger/SwaggerConfig.java new file mode 100644 index 0000000..3ceb506 --- /dev/null +++ b/src/main/java/com/w/t/common/swagger/SwaggerConfig.java @@ -0,0 +1,40 @@ +package com.w.t.common.swagger; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.*; +import springfox.documentation.service.*; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger.web.ApiKeyVehicle; +import java.util.Collections; +import java.util.List; + +@Configuration +public class SwaggerConfig { + + @Bean + public Docket createRestApi() { + return new Docket(DocumentationType.OAS_30) + .apiInfo(apiInfo()) + .select() + .apis(RequestHandlerSelectors.basePackage("tech.shoubi.aft")) + .paths(PathSelectors.any()) + .build() + .securitySchemes(security()); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("AFT Api Document") + .version("1.0") + .build(); + } + + private List security() { + ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header"); + return Collections.singletonList(apiKey); + } + + +} diff --git a/src/main/java/com/w/t/common/util/storage/LocalStorageUtil.java b/src/main/java/com/w/t/common/util/storage/LocalStorageUtil.java new file mode 100644 index 0000000..12f18be --- /dev/null +++ b/src/main/java/com/w/t/common/util/storage/LocalStorageUtil.java @@ -0,0 +1,39 @@ +package com.w.t.common.util.storage; + +import com.w.t.common.core.constant.LocalStorageConstant; + +import java.util.HashMap; +import java.util.Map; + +public class LocalStorageUtil { + + private static ThreadLocal> threadLocal = new ThreadLocal() { + protected Map initialValue() { + return new HashMap(); + } + }; + + private static void set(String key, Object value) { + Map map = (Map) threadLocal.get(); + map.put(key, value); + } + + private static T get(String key) { + Map map = (Map) threadLocal.get(); + return (T) map.get(key); + } + + public static void clean(){ + threadLocal.remove(); + } + + public static void setCurrentUserId(Long userId) { + set(LocalStorageConstant.CURRENT_USER, userId); + } + + public static Long getCurrentUserId() { + return get(LocalStorageConstant.CURRENT_USER); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/w/t/module/controller/AuthController.java b/src/main/java/com/w/t/module/controller/AuthController.java new file mode 100644 index 0000000..ece91b8 --- /dev/null +++ b/src/main/java/com/w/t/module/controller/AuthController.java @@ -0,0 +1,43 @@ +package com.w.t.module.controller; + +import com.w.t.module.entity.dto.LoginBody; +import com.w.t.module.entity.dto.RegisterBody; +import com.w.t.module.service.IUserService; +import io.swagger.annotations.Api; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import com.w.t.common.core.response.R; +import com.w.t.common.util.storage.LocalStorageUtil; + +/** + * @author zhangxiang + * @date 2023/02/09 + */ +@Api(value = "鉴权管理") +@RestController +@RequestMapping("/auth") +public class AuthController { + + @Autowired + IUserService userService; + + @PostMapping("/register") + public R register(@RequestBody RegisterBody registerBody) { + userService.registerUser(registerBody.getUsername(), registerBody.getPassword()); + return R.success(); + } + + @PostMapping("/login") + public R login(@RequestBody LoginBody loginBody) { + String token = userService.login(loginBody.getUsername(), loginBody.getPassword()); + return R.success(token); + } + + @GetMapping("/logout") + public R logout() { + Long userId = LocalStorageUtil.getCurrentUserId(); + userService.logout(userId); + return R.success(); + } + +} diff --git a/src/main/java/com/w/t/module/controller/FollowController.java b/src/main/java/com/w/t/module/controller/FollowController.java new file mode 100644 index 0000000..787ffca --- /dev/null +++ b/src/main/java/com/w/t/module/controller/FollowController.java @@ -0,0 +1,49 @@ +package com.w.t.module.controller; + +import com.w.t.module.entity.User; +import com.w.t.module.service.impl.FollowService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import com.w.t.common.core.response.R; +import com.w.t.common.util.storage.LocalStorageUtil; + +import java.util.List; + +/** + * @author zhangxiang + * @date 2023/02/09 + */ +@Api(value = "关注管理") +@RestController +@RequestMapping("/follow") +public class FollowController { + + @Autowired + private FollowService followService; + + @ApiOperation("subscribe a user") + @PostMapping("/subscribe") + public R subscribe(@RequestParam Long followerId) { + Long followingId = LocalStorageUtil.getCurrentUserId(); + followService.subscribe(followerId, followingId); + return R.success(); + } + + @ApiOperation("unsubscribe a user") + @PostMapping("/unsubscribe") + public R unsubscribe(@RequestParam Long followerId) { + Long followingId = LocalStorageUtil.getCurrentUserId(); + followService.unsubscribe(followerId, followingId); + return R.success(); + } + + @ApiOperation("query nearby users") + @PostMapping("/nearby") + public R> nearby(@RequestParam String userName) { + List userList=followService.nearbyUsers(userName); + return R.success(userList); + } + +} diff --git a/src/main/java/com/w/t/module/controller/UserController.java b/src/main/java/com/w/t/module/controller/UserController.java new file mode 100644 index 0000000..4ce197f --- /dev/null +++ b/src/main/java/com/w/t/module/controller/UserController.java @@ -0,0 +1,67 @@ +package com.w.t.module.controller; + +import com.w.t.common.core.response.R; +import com.w.t.common.core.web.BaseController; +import com.w.t.module.entity.User; +import com.w.t.module.entity.dto.UserDTO; +import com.w.t.module.service.IUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * @author zhangxiang + * @date 2023/02/09 + */ + +@Api(value = "user api") +@RestController +@RequestMapping("/users") +public class UserController extends BaseController { + + private final IUserService userService; + + public UserController(IUserService userService) { + this.userService = userService; + } + + @ApiOperation("create a user") + @PostMapping + public R createUser(@RequestBody UserDTO userDTO) { + User user=new User(); + BeanUtils.copyProperties(userDTO,user); + int result = userService.createUser(user); + return R.success(result); + } + + @ApiOperation("get user by id") + @GetMapping("{id}") + public R getUserById(@PathVariable long id) { + User user = userService.getUserById(id); + return R.success(user); + } + + @ApiOperation("update user by id") + @PutMapping("{id}") + public R updateUserById(@PathVariable long id, @RequestBody UserDTO userDTO) { + User user=new User(); + BeanUtils.copyProperties(userDTO,user); + return R.success(userService.updateUserById(id, user)); + } + + @ApiOperation("delete user by user id") + @DeleteMapping("/{userId}") + public R deleteUser(@PathVariable("userId") Long userId) { + return R.success(userService.removeUserById(userId)); + } + + @ApiOperation("get all users") + @GetMapping + public R> getAllUsers() { + List users = userService.getAllUsers(); + return R.success(users); + } +} diff --git a/src/main/java/com/w/t/module/entity/Follow.java b/src/main/java/com/w/t/module/entity/Follow.java new file mode 100644 index 0000000..ac66b9b --- /dev/null +++ b/src/main/java/com/w/t/module/entity/Follow.java @@ -0,0 +1,20 @@ +package com.w.t.module.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.w.t.common.core.web.BaseEntity; +import lombok.Data; + +@Data +@TableName("follow_user") +public class Follow extends BaseEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + private Long followerId; + + private Long followingId; + +} diff --git a/src/main/java/com/w/t/module/entity/User.java b/src/main/java/com/w/t/module/entity/User.java new file mode 100644 index 0000000..ebf81bf --- /dev/null +++ b/src/main/java/com/w/t/module/entity/User.java @@ -0,0 +1,34 @@ +package com.w.t.module.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.w.t.common.core.web.BaseEntity; +import lombok.Data; + +/** + * @author zhangxiang + * @date 2023/02/09 + */ +@Data +@TableName("account_user") +public class User extends BaseEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + private String name; + + private String password; + + private String dob; + + private String address; + + private String description; + + private Double longitude; + + private Double latitude; + +} diff --git a/src/main/java/com/w/t/module/entity/dto/LoginBody.java b/src/main/java/com/w/t/module/entity/dto/LoginBody.java new file mode 100644 index 0000000..beaa387 --- /dev/null +++ b/src/main/java/com/w/t/module/entity/dto/LoginBody.java @@ -0,0 +1,14 @@ +package com.w.t.module.entity.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +@Data +public class LoginBody { + + private String username; + + private String password; + +} diff --git a/src/main/java/com/w/t/module/entity/dto/RegisterBody.java b/src/main/java/com/w/t/module/entity/dto/RegisterBody.java new file mode 100644 index 0000000..56da3d7 --- /dev/null +++ b/src/main/java/com/w/t/module/entity/dto/RegisterBody.java @@ -0,0 +1,12 @@ +package com.w.t.module.entity.dto; + +import lombok.Data; + +@Data +public class RegisterBody { + + private String username; + + private String password; + +} diff --git a/src/main/java/com/w/t/module/entity/dto/UserDTO.java b/src/main/java/com/w/t/module/entity/dto/UserDTO.java new file mode 100644 index 0000000..ed88e0d --- /dev/null +++ b/src/main/java/com/w/t/module/entity/dto/UserDTO.java @@ -0,0 +1,20 @@ +package com.w.t.module.entity.dto; + +import lombok.Data; + +@Data +public class UserDTO { + + private String name; + + private String dob; + + private String address; + + private String description; + + private Double longitude; + + private Double latitude; + +} diff --git a/src/main/java/com/w/t/module/mapper/FollowMapper.java b/src/main/java/com/w/t/module/mapper/FollowMapper.java new file mode 100644 index 0000000..740c66b --- /dev/null +++ b/src/main/java/com/w/t/module/mapper/FollowMapper.java @@ -0,0 +1,16 @@ +package com.w.t.module.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.w.t.module.entity.Follow; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface FollowMapper extends BaseMapper { + + @Select("select following_id from follow_user where follower_id=${followerId}") + List selectFollowersByFollowingId(Long followerId); + +} diff --git a/src/main/java/com/w/t/module/mapper/UserMapper.java b/src/main/java/com/w/t/module/mapper/UserMapper.java new file mode 100644 index 0000000..d29156a --- /dev/null +++ b/src/main/java/com/w/t/module/mapper/UserMapper.java @@ -0,0 +1,14 @@ +package com.w.t.module.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import com.w.t.module.entity.User; + +import java.util.List; + +@Mapper +public interface UserMapper extends BaseMapper { + @Select("SELECT * FROM account_user WHERE 1=1 and (longitude BETWEEN ${minlng} AND ${maxlng}) and (latitude BETWEEN ${minlat} AND ${maxlat})") + List select(Double minlng, Double maxlng, Double minlat, Double maxlat); +} diff --git a/src/main/java/com/w/t/module/service/IFollowService.java b/src/main/java/com/w/t/module/service/IFollowService.java new file mode 100644 index 0000000..734f06b --- /dev/null +++ b/src/main/java/com/w/t/module/service/IFollowService.java @@ -0,0 +1,15 @@ +package com.w.t.module.service; + +import com.w.t.module.entity.User; + +import java.util.List; + +public interface IFollowService { + + void subscribe(Long followerId,Long followingId); + + void unsubscribe(Long followerId,Long followingId); + + List nearbyUsers(String username); + +} diff --git a/src/main/java/com/w/t/module/service/IUserService.java b/src/main/java/com/w/t/module/service/IUserService.java new file mode 100644 index 0000000..fb1f50d --- /dev/null +++ b/src/main/java/com/w/t/module/service/IUserService.java @@ -0,0 +1,70 @@ +package com.w.t.module.service; + +import com.w.t.module.entity.User; + +import java.util.List; + +public interface IUserService { + + /** + * register a user + * @param username + * @param password + */ + void registerUser(String username, String password); + /** + * the user login + * @param username + * @param password + */ + String login(String username, String password); + + /** + * the user logout + * @param userId + */ + void logout(Long userId); + + /** + * create a user + * @param user + * @return + */ + int createUser(User user); + + /** + * get user by id + * @param userId + * @return + */ + User getUserById(Long userId); + + /** + * get user by name + * @param username + * @return + */ + User getUserByName(String username); + + /** + * get all users + * @return + */ + List getAllUsers(); + + /** + * modify user info + * @param userId + * @param user + * @return + */ + Integer updateUserById(Long userId, User user); + + /** + * delete a user by id + * @param userId + * @return + */ + Integer removeUserById(Long userId); + +} diff --git a/src/main/java/com/w/t/module/service/impl/FollowService.java b/src/main/java/com/w/t/module/service/impl/FollowService.java new file mode 100644 index 0000000..76ce9c4 --- /dev/null +++ b/src/main/java/com/w/t/module/service/impl/FollowService.java @@ -0,0 +1,89 @@ +package com.w.t.module.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.w.t.module.service.IFollowService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.w.t.module.entity.Follow; +import com.w.t.module.entity.User; +import com.w.t.module.mapper.FollowMapper; +import com.w.t.module.mapper.UserMapper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Service +public class FollowService implements IFollowService { + + @Autowired + UserMapper userMapper; + + @Autowired + FollowMapper followMapper; + + @Override + public void subscribe(Long followerId, Long followingId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Follow::getFollowerId, followerId); + wrapper.eq(Follow::getFollowingId, followingId); + int followCount = followMapper.selectCount(wrapper); + if (followCount > 0) { + throw new RuntimeException("the user has already followed."); + } + Follow follow = new Follow(); + follow.setFollowerId(followerId); + follow.setFollowingId(followingId); + followMapper.insert(follow); + } + + @Override + public void unsubscribe(Long followerId, Long followingId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Follow::getFollowerId, followerId); + wrapper.eq(Follow::getFollowingId, followingId); + Follow follow = followMapper.selectOne(wrapper); + if (Objects.isNull(follow)) { + throw new RuntimeException("the following does not followed."); + } + followMapper.deleteById(follow.getId()); + } + + @Override + public List nearbyUsers(String username) { + //query the user's longitude or latitude by name + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(User::getName, username); + User user = userMapper.selectOne(wrapper); + if (Objects.isNull(user)) { + throw new RuntimeException("the user can't be find."); + } + + double latitude = user.getLatitude(); + double longitude = user.getLongitude(); + + //query nearby user list + double dlng = 2 * Math.asin(Math.sin(1 / (2 * 6371)) / Math.cos(latitude * Math.PI / 180)); + dlng = dlng * 180 / Math.PI; + double dlat = 1 / 6371; + dlat = dlat * 180 / Math.PI; + double minlat = latitude - dlat; + double maxlat = latitude + dlat; + double minlng = longitude - dlng; + double maxlng = longitude + dlng; + List nearbyUsers = userMapper.select(minlng, maxlng, minlat, maxlat); + + //query nearby following list + List follows = followMapper.selectFollowersByFollowingId(user.getId()); + List result = new ArrayList<>(); + for (User u : nearbyUsers) { + for (Long followingId : follows) { + if (followingId.equals(u.getId())) { + result.add(u); + } + } + } + return result; + } + +} diff --git a/src/main/java/com/w/t/module/service/impl/UserService.java b/src/main/java/com/w/t/module/service/impl/UserService.java new file mode 100644 index 0000000..82b750d --- /dev/null +++ b/src/main/java/com/w/t/module/service/impl/UserService.java @@ -0,0 +1,144 @@ +package com.w.t.module.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.w.t.common.core.constant.LocalStorageConstant; +import com.w.t.common.security.JwtUtil; +import com.w.t.module.entity.User; +import com.w.t.common.redis.RedisManager; +import com.w.t.module.mapper.UserMapper; +import com.w.t.module.service.IUserService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.w.t.module.util.PasswordUtil; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +@Service +public class UserService implements IUserService { + + @Autowired + UserMapper userMapper; + + @Autowired + RedisManager redisManager; + + @Override + public void registerUser(String username, String password) { + if (StringUtils.isEmpty(username)) { + throw new RuntimeException("the user name can't be empty."); + } + if (StringUtils.isEmpty(password)) { + throw new RuntimeException("the user password can't be empty."); + } + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(User::getName, username); + int userCount = userMapper.selectCount(lambdaQueryWrapper); + if (userCount > 0) { + throw new RuntimeException("the user already existed."); + } + + String encryptedPassword = PasswordUtil.encryptPassword(password, LocalStorageConstant.USER_SALT); + User user = new User(); + user.setName(username); + user.setPassword(encryptedPassword); + userMapper.insert(user); + } + + @Override + public String login(String username, String password) { + if (StringUtils.isEmpty(username)) { + throw new RuntimeException("the user name can't be empty."); + } + if (StringUtils.isEmpty(password)) { + throw new RuntimeException("the user password can't be empty."); + } + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(User::getName, username); + User user = userMapper.selectOne(lambdaQueryWrapper); + if (Objects.isNull(user)) { + throw new RuntimeException("the user's name can't be find."); + } + + String encryptedPassword = PasswordUtil.encryptPassword(password, LocalStorageConstant.USER_SALT); + if (!user.getPassword().equals(encryptedPassword)) { + throw new RuntimeException("the user's password can't be matched"); + } + + String loginKey = UUID.randomUUID().toString(); + String token = JwtUtil.createToken(loginKey, user.getId()); + String cacheKey = String.valueOf(user.getId()) + ":" + user.getName(); + redisManager.setCacheMapValue(cacheKey, loginKey, token); + return token; + } + + @Override + public void logout(Long userId) { + User user = userMapper.selectById(userId); + if (Objects.nonNull(user)) { + String cacheKey = String.valueOf(user.getId()) + ":" + user.getName(); + redisManager.deleteObject(cacheKey); + } + } + + @Override + public int createUser(User user) { + //check the user's name valid. + if (StringUtils.isEmpty(user.getName())) { + throw new RuntimeException("the user name can't be empty."); + } + //check the user weather already existed. + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(User::getName, user.getName()); + int userCount = userMapper.selectCount(lambdaQueryWrapper); + if (userCount > 0) { + throw new RuntimeException("user already existed."); + } + return userMapper.insert(user); + } + + @Override + public User getUserById(Long userId) { + return userMapper.selectById(userId); + } + + @Override + public User getUserByName(String username) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(User::getName, username); + return userMapper.selectOne(lambdaQueryWrapper); + } + + @Override + public List getAllUsers() { + return userMapper.selectList(new QueryWrapper<>()); + } + + @Override + public Integer updateUserById(Long userId, User user) { + //check the user's id valid. + if (0L == userId) { + throw new RuntimeException("the user id is not valid."); + } + //query the user info by user id + User sourceUser = userMapper.selectById(userId); + if (Objects.isNull(user)) { + throw new RuntimeException("the user can't be find."); + } + //replace update info and update db + BeanUtils.copyProperties(user, sourceUser); + return userMapper.updateById(sourceUser); + } + + @Override + public Integer removeUserById(Long userId) { + return userMapper.deleteById(userId); + } + +} diff --git a/src/main/java/com/w/t/module/util/ConstantUtil.java b/src/main/java/com/w/t/module/util/ConstantUtil.java new file mode 100644 index 0000000..881c79c --- /dev/null +++ b/src/main/java/com/w/t/module/util/ConstantUtil.java @@ -0,0 +1,4 @@ +package com.w.t.module.util; + +public class ConstantUtil { +} diff --git a/src/main/java/com/w/t/module/util/PasswordUtil.java b/src/main/java/com/w/t/module/util/PasswordUtil.java new file mode 100644 index 0000000..740e03f --- /dev/null +++ b/src/main/java/com/w/t/module/util/PasswordUtil.java @@ -0,0 +1,15 @@ +package com.w.t.module.util; + +import org.apache.shiro.crypto.hash.SimpleHash; + +public class PasswordUtil { + + private final static String algorithmName = "md5"; + + private final static Integer hashIterations = 1024; + + public static String encryptPassword(String password, String salt) { + return new SimpleHash(algorithmName, password, salt, hashIterations).toString(); + } + +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..80553d2 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,20 @@ +spring: + datasource: + url: jdbc:p6spy:mysql://127.0.0.1:3306/wiredcraft?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8 + username: root + password: root + driver-class-name: com.p6spy.engine.spy.P6SpyDriver + + redis: + host: 127.0.0.1 + port: 6379 + password: H3yuncom + + flyway: + enabled: true + baseline-on-migrate: true + +logging: + level: + com.baomidou.mybatisplus.samples: debug + tech.shoubi.aft: debug \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..a7835d1 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,20 @@ +server: + port: 9001 + +spring: + profiles: + active: dev + main: + banner-mode: off + +springfox: + documentation: + swagger-ui: + enabled: true + +mybatis-plus: + global-config: + banner: false +file: + upload: + path: C:/ \ No newline at end of file diff --git a/src/main/resources/db/migration/V1.0.1__init_table.sql b/src/main/resources/db/migration/V1.0.1__init_table.sql new file mode 100644 index 0000000..a0ab46a --- /dev/null +++ b/src/main/resources/db/migration/V1.0.1__init_table.sql @@ -0,0 +1,37 @@ +DROP TABLE IF EXISTS `account_user`; +CREATE TABLE `account_user` +( + `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'user name', + `password` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'user password', + `dob` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'date of birth', + `address` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'user address', + `description` varchar(0) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'user description', + `longitude` decimal(18, 15) NULL DEFAULT NULL COMMENT 'user location longitude', + `latitude` double(18, 15 +) NULL DEFAULT NULL COMMENT 'user location latitude', + `create_at` timestamp(0) NULL DEFAULT NULL COMMENT 'create date', + `update_at` timestamp(0) NULL DEFAULT NULL COMMENT 'update date', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +INSERT INTO `account_user`(`id`, `name`, `password`, `dob`, `address`, `description`, `longitude`, `latitude`, `create_at`, `update_at`) VALUES (1, 'zhangxiang1', 'e398a3701330607fb15816edcd4dbfd7', 'zhangxiang', NULL, NULL, 116.310771000000000, 40.062630000000000, '2023-02-13 07:39:45', '2023-02-13 08:40:21'); +INSERT INTO `account_user`(`id`, `name`, `password`, `dob`, `address`, `description`, `longitude`, `latitude`, `create_at`, `update_at`) VALUES (2, 'zhangxiang2', 'e398a3701330607fb15816edcd4dbfd7', 'zhangxiang', NULL, NULL, 116.310772000000000, 40.062630000000000, '2023-02-13 07:39:45', '2023-02-13 08:40:21'); +INSERT INTO `account_user`(`id`, `name`, `password`, `dob`, `address`, `description`, `longitude`, `latitude`, `create_at`, `update_at`) VALUES (3, 'zhangxiang3', 'e398a3701330607fb15816edcd4dbfd7', 'zhangxiang', NULL, NULL, 116.310773000000000, 40.062630000000000, '2023-02-13 07:39:45', '2023-02-13 08:40:21'); +INSERT INTO `account_user`(`id`, `name`, `password`, `dob`, `address`, `description`, `longitude`, `latitude`, `create_at`, `update_at`) VALUES (4, 'zhangxiang4', 'e398a3701330607fb15816edcd4dbfd7', 'zhangxiang', NULL, NULL, 116.310774000000000, 40.062630000000000, '2023-02-13 07:39:45', '2023-02-13 08:40:21'); +INSERT INTO `account_user`(`id`, `name`, `password`, `dob`, `address`, `description`, `longitude`, `latitude`, `create_at`, `update_at`) VALUES (5, 'zhangxiang5', 'e398a3701330607fb15816edcd4dbfd7', 'zhangxiang', NULL, NULL, 116.310775000000000, 40.062630000000000, '2023-02-13 07:39:45', '2023-02-13 08:40:21'); + +DROP TABLE IF EXISTS `follow_user`; +CREATE TABLE `follow_user` +( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID', + `follower_id` int NOT NULL COMMENT 'follower user id', + `following_id` int NOT NULL COMMENT 'following user id', + `create_at` timestamp NULL DEFAULT NULL COMMENT 'create date', + `update_at` timestamp NULL DEFAULT NULL COMMENT 'update date', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC; + +INSERT INTO `follow_user`(`id`, `follower_id`, `following_id`, `create_at`, `update_at`) VALUES (1, 1, 2, '2023-02-13 07:39:45', '2023-02-13 07:39:45'); +INSERT INTO `follow_user`(`id`, `follower_id`, `following_id`, `create_at`, `update_at`) VALUES (2, 1, 3, '2023-02-13 07:39:45', '2023-02-13 07:39:45'); +INSERT INTO `follow_user`(`id`, `follower_id`, `following_id`, `create_at`, `update_at`) VALUES (3, 1, 4, '2023-02-13 07:39:45', '2023-02-13 07:39:45'); diff --git a/src/main/resources/spy.properties b/src/main/resources/spy.properties new file mode 100644 index 0000000..0fbe28d --- /dev/null +++ b/src/main/resources/spy.properties @@ -0,0 +1,21 @@ +modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory +# 自定义日志打印 +logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger +#日志输出到控制台 +appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger +# 使用日志系统记录 sql +#appender=com.p6spy.engine.spy.appender.Slf4JLogger +# 设置 p6spy driver 代理 +deregisterdrivers=true +# 取消JDBC URL前缀 +useprefix=true +# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. +excludecategories=info,debug,result,commit,resultset +# 日期格式 +dateformat=yyyy-MM-dd HH:mm:ss +# 实际驱动可多个 +#driverlist=org.h2.Driver +# 是否开启慢SQL记录 +outagedetection=true +# 慢SQL记录标准 2 秒 +outagedetectioninterval=2 diff --git a/src/test/java/com/w/t/module/service/FollowServiceTest.java b/src/test/java/com/w/t/module/service/FollowServiceTest.java new file mode 100644 index 0000000..7022543 --- /dev/null +++ b/src/test/java/com/w/t/module/service/FollowServiceTest.java @@ -0,0 +1,29 @@ +package com.w.t.module.service; + +import com.w.t.module.service.impl.FollowService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class FollowServiceTest { + + @Autowired + private FollowService followService; + + @Test + void subscribe() { + followService.subscribe(1L,7L); + } + + @Test + void unsubscribe() { + followService.unsubscribe(1L,5L); + } + + @Test + void nearbyUsers() { + followService.nearbyUsers("zhangxiang1"); + } + +} diff --git a/src/test/java/com/w/t/module/service/UserServiceTest.java b/src/test/java/com/w/t/module/service/UserServiceTest.java new file mode 100644 index 0000000..a0acd39 --- /dev/null +++ b/src/test/java/com/w/t/module/service/UserServiceTest.java @@ -0,0 +1,55 @@ +package com.w.t.module.service; + +import org.junit.Assert; +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import com.w.t.module.entity.User; +import com.w.t.module.entity.dto.UserDTO; +import com.w.t.module.service.impl.UserService; + +@SpringBootTest +public class UserServiceTest { + + @Autowired + private UserService userService; + + @Test + void registerUser() { + userService.registerUser("zhangxiang1", "123"); + User user = userService.getUserByName("zhangxiang1"); + Assert.assertNotNull("the user register failed", user); + userService.removeUserById(user.getId()); + } + + @Test + void login() { + String token = userService.login("zhangxiang", "123"); + Assert.assertNotNull("the user login failed",token); + } + + @Test + void createUser() { + UserDTO userDTO=new UserDTO(); + userDTO.setName("zhangxiang2"); + userDTO.setAddress("address"); + userDTO.setDescription("test user"); + userDTO.setDob("20221212"); + userDTO.setLatitude(12.123); + userDTO.setLongitude(112.21); + User user=new User(); + BeanUtils.copyProperties(userDTO,user); + userService.createUser(user); + User user1 = userService.getUserByName("zhangxiang1"); + Assert.assertNotNull("create a user failed", user1); + } + + @Test + void getUserById() { + User user1 = userService.getUserById(1L); + Assert.assertNotNull("the user does not existed.", user1); + } + +}