├── .idea
├── .gitignore
├── JavaSceneConfigState.xml
├── compiler.xml
├── dataSources.xml
├── encodings.xml
├── jarRepositories.xml
├── misc.xml
├── mybatisx
│ └── templates.xml
├── uiDesigner.xml
└── vcs.xml
├── LICENSE
├── pom.xml
├── shortlink-admin
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── enndfp
│ │ │ └── shortlink
│ │ │ └── admin
│ │ │ ├── ShortLinkAdminApplication.java
│ │ │ ├── common
│ │ │ ├── constant
│ │ │ │ └── RedisCacheConstant.java
│ │ │ ├── convention
│ │ │ │ ├── errorcode
│ │ │ │ │ ├── ErrorCode.java
│ │ │ │ │ └── IErrorCode.java
│ │ │ │ ├── exception
│ │ │ │ │ ├── AbstractException.java
│ │ │ │ │ ├── ClientException.java
│ │ │ │ │ ├── GlobalExceptionHandler.java
│ │ │ │ │ ├── RemoteException.java
│ │ │ │ │ └── ServerException.java
│ │ │ │ └── result
│ │ │ │ │ └── Result.java
│ │ │ └── serialize
│ │ │ │ └── PhoneDesensitizationSerializer.java
│ │ │ ├── config
│ │ │ ├── InterceptorConfig.java
│ │ │ ├── MyBatisPlusConfig.java
│ │ │ └── RbloomFilterConfig.java
│ │ │ ├── context
│ │ │ ├── UserContext.java
│ │ │ └── UserContextDTO.java
│ │ │ ├── controller
│ │ │ ├── GroupController.java
│ │ │ ├── LinkController.java
│ │ │ └── UserController.java
│ │ │ ├── dao
│ │ │ ├── entity
│ │ │ │ ├── GroupDO.java
│ │ │ │ └── UserDO.java
│ │ │ └── mapper
│ │ │ │ ├── GroupMapper.java
│ │ │ │ └── UserMapper.java
│ │ │ ├── dto
│ │ │ ├── req
│ │ │ │ ├── group
│ │ │ │ │ ├── GroupAddReqDTO.java
│ │ │ │ │ ├── GroupSortReqDTO.java
│ │ │ │ │ └── GroupUpdateReqDTO.java
│ │ │ │ └── user
│ │ │ │ │ ├── UserLoginReqDTO.java
│ │ │ │ │ ├── UserRegisterReqDTO.java
│ │ │ │ │ └── UserUpdateReqDTO.java
│ │ │ └── resp
│ │ │ │ ├── group
│ │ │ │ └── GroupRespDTO.java
│ │ │ │ └── user
│ │ │ │ ├── UserActualRespDTO.java
│ │ │ │ ├── UserLoginRespDTO.java
│ │ │ │ └── UserRespDTO.java
│ │ │ ├── interceptor
│ │ │ └── UserContextInterceptor.java
│ │ │ ├── remote
│ │ │ └── dto
│ │ │ │ ├── LinkRemoteService.java
│ │ │ │ ├── req
│ │ │ │ └── link
│ │ │ │ │ ├── LinkCreateReqDTO.java
│ │ │ │ │ └── LinkPageReqDTO.java
│ │ │ │ └── resp
│ │ │ │ └── link
│ │ │ │ ├── LinkCountRespDTO.java
│ │ │ │ ├── LinkCreateRespDTO.java
│ │ │ │ └── LinkPageRespDTO.java
│ │ │ ├── service
│ │ │ ├── GroupService.java
│ │ │ ├── UserService.java
│ │ │ └── impl
│ │ │ │ ├── GroupServiceImpl.java
│ │ │ │ └── UserServiceImpl.java
│ │ │ └── utils
│ │ │ ├── RandomStringUtil.java
│ │ │ ├── ResultUtil.java
│ │ │ └── ThrowUtil.java
│ └── resources
│ │ ├── application.yml
│ │ ├── shardingsphere-config-dev.yml
│ │ └── shardingsphere-config-prod.yml
│ └── test
│ └── java
│ └── com
│ └── enndfp
│ └── shortlink
│ └── admin
│ └── UserTableShardingTest.java
├── shortlink-gateway
├── pom.xml
└── src
│ └── main
│ └── resources
│ └── application.yml
└── shortlink-project
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── enndfp
│ │ └── shortlink
│ │ └── project
│ │ ├── ShortLinkApplication.java
│ │ ├── common
│ │ └── convention
│ │ │ ├── errorcode
│ │ │ ├── ErrorCode.java
│ │ │ └── IErrorCode.java
│ │ │ ├── exception
│ │ │ ├── AbstractException.java
│ │ │ ├── ClientException.java
│ │ │ ├── GlobalExceptionHandler.java
│ │ │ ├── RemoteException.java
│ │ │ └── ServerException.java
│ │ │ └── result
│ │ │ └── Result.java
│ │ ├── config
│ │ ├── MyBatisPlusConfig.java
│ │ └── RbloomFilterConfig.java
│ │ ├── controller
│ │ └── LinkController.java
│ │ ├── dao
│ │ ├── entity
│ │ │ └── LinkDO.java
│ │ └── mapper
│ │ │ └── LinkMapper.java
│ │ ├── dto
│ │ ├── req
│ │ │ └── link
│ │ │ │ ├── LinkCreateReqDTO.java
│ │ │ │ └── LinkPageReqDTO.java
│ │ └── resp
│ │ │ └── link
│ │ │ ├── LinkCountRespDTO.java
│ │ │ ├── LinkCreateRespDTO.java
│ │ │ └── LinkPageRespDTO.java
│ │ ├── service
│ │ ├── LinkService.java
│ │ └── impl
│ │ │ └── LinkServiceImpl.java
│ │ └── utils
│ │ ├── HashUtil.java
│ │ ├── ResultUtil.java
│ │ └── ThrowUtil.java
└── resources
│ ├── application.yml
│ ├── shardingsphere-config-dev.yml
│ └── shardingsphere-config-prod.yml
└── test
└── java
└── com
└── enndfp
└── shortlink
└── project
└── LinkTableShardingTest.java
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/JavaSceneConfigState.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/dataSources.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | mysql.8
6 | true
7 | com.mysql.cj.jdbc.Driver
8 | jdbc:mysql://localhost:3306
9 | $ProjectFileDir$
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/mybatisx/templates.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/.idea/uiDesigner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
8 | -
9 |
10 |
11 | -
12 |
13 |
14 | -
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 | -
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 | -
103 |
104 |
105 | -
106 |
107 |
108 | -
109 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Enndfp
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.enndfp.shortlink
8 | shortlink
9 | pom
10 | 1.0-SNAPSHOT
11 |
12 |
13 | shortlink-admin
14 | shortlink-gateway
15 | shortlink-project
16 |
17 |
18 |
19 | 17
20 | 3.0.7
21 | 2022.0.3
22 | 2022.0.0.0-RC2
23 | 3.0.2
24 | 5.3.2
25 | 0.9.1
26 | 2.0.36
27 | 3.5.3.1
28 | 6.5.2
29 | 5.8.20
30 | 3.21.3
31 | 30.0-jre
32 |
33 |
34 |
35 |
36 | org.projectlombok
37 | lombok
38 |
39 |
40 |
41 |
42 |
43 |
44 | org.springframework.boot
45 | spring-boot-dependencies
46 | ${spring-boot.version}
47 | pom
48 | import
49 |
50 |
51 |
52 | org.springframework.cloud
53 | spring-cloud-dependencies
54 | ${spring-cloud.version}
55 | pom
56 | import
57 |
58 |
59 |
60 | com.alibaba.cloud
61 | spring-cloud-alibaba-dependencies
62 | ${spring-cloud-alibaba.version}
63 | pom
64 | import
65 |
66 |
67 |
68 | com.baomidou
69 | mybatis-plus-boot-starter
70 | ${mybatis-plus.version}
71 |
72 |
73 |
74 | org.apache.shardingsphere
75 | shardingsphere-jdbc-core
76 | ${shardingsphere.version}
77 |
78 |
79 |
80 | io.jsonwebtoken
81 | jjwt
82 | ${jjwt.version}
83 |
84 |
85 |
86 | com.alibaba.fastjson2
87 | fastjson2
88 | ${fastjson2.version}
89 |
90 |
91 |
92 | com.github.dozermapper
93 | dozer-core
94 | ${dozer-core.version}
95 |
96 |
97 |
98 | cn.hutool
99 | hutool-all
100 | ${hutool-all.version}
101 |
102 |
103 |
104 | org.redisson
105 | redisson-spring-boot-starter
106 | ${redisson.version}
107 |
108 |
109 |
110 | com.google.guava
111 | guava
112 | ${guava.version}
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | org.apache.maven.plugins
121 | maven-compiler-plugin
122 | 3.6.1
123 |
124 | 17
125 | 17
126 |
127 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/shortlink-admin/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | shortlink
7 | com.enndfp.shortlink
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | shortlink-admin
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-web
18 |
19 |
20 |
21 | com.baomidou
22 | mybatis-plus-boot-starter
23 |
24 |
25 |
26 | com.mysql
27 | mysql-connector-j
28 | runtime
29 |
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-starter-jdbc
34 |
35 |
36 |
37 | cn.hutool
38 | hutool-all
39 |
40 |
41 |
42 | org.springframework.boot
43 | spring-boot-starter-data-redis
44 |
45 |
46 |
47 | org.redisson
48 | redisson-spring-boot-starter
49 |
50 |
51 |
52 | org.apache.shardingsphere
53 | shardingsphere-jdbc-core
54 |
55 |
56 |
57 | com.alibaba.fastjson2
58 | fastjson2
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/ShortLinkAdminApplication.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin;
2 |
3 | import org.mybatis.spring.annotation.MapperScan;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 |
7 | /**
8 | * @author Enndfp
9 | */
10 | @SpringBootApplication
11 | @MapperScan("com.enndfp.shortlink.admin.dao.mapper")
12 | public class ShortLinkAdminApplication {
13 |
14 | public static void main(String[] args) {
15 | SpringApplication.run(ShortLinkAdminApplication.class,args);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/common/constant/RedisCacheConstant.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.common.constant;
2 |
3 | /**
4 | * Redis缓存常量
5 | *
6 | * @author Enndfp
7 | */
8 | public class RedisCacheConstant {
9 |
10 | /**
11 | * 用户注册锁
12 | */
13 | public static final String LOCK_USER_REGISTER_KEY = "short-link:lock:user-register:";
14 |
15 | /**
16 | * 用户登录token
17 | */
18 | public static final String USER_LOGIN_KEY = "user:login:";
19 |
20 | /**
21 | * 用户登录token过期时间
22 | */
23 | public static final Long USER_LOGIN_TTL = 30L;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/common/convention/errorcode/ErrorCode.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.common.convention.errorcode;
2 |
3 | /**
4 | * 错误码定义
5 | *
6 | * @author Enndfp
7 | */
8 | public enum ErrorCode implements IErrorCode {
9 |
10 | // ========== 一级宏观错误码 客户端错误 ==========
11 | CLIENT_ERROR("A000001", "客户端请求错误"),
12 |
13 | // ========== 二级宏观错误码 ==========
14 | USER_NAME_NULL("A000100", "用户名为空"),
15 | PASSWORD_NULL("A000101", "密码为空"),
16 | GROUP_NAME_NULL("A000201", "分组名称为空"),
17 |
18 |
19 | // ========== 一级宏观错误码 系统执行出错 ==========
20 | SERVICE_ERROR("B000001", "服务端错误"),
21 |
22 | // ========== 二级宏观错误码 ==========
23 | USER_NOT_EXIST("B000100", "用户不存在"),
24 | USER_NAME_EXIST("B000101", "用户名已存在"),
25 | USER_REGISTER_ERROR("B000102", "用户保存失败"),
26 | USER_INFO_UPDATE_ERROR("B000103", "用户信息更新失败"),
27 | USER_HAS_LOGIN("B000104", "用户已登录"),
28 | USER_OR_TOKEN_NULL("B000105", "用户不存在或token已过期"),
29 | USER_NOT_LOGIN("B000106", "用户未登录"),
30 | GROUP_SAVE_ERROR("B000201", "分组保存失败"),
31 | GROUP_UPDATE_ERROR("B000202", "分组修改失败"),
32 | GROUP_DELETE_ERROR("B000203", "分组删除失败"),
33 | GROUP_SORT_ERROR("B000203", "分组排序失败"),
34 |
35 |
36 | // ========== 一级宏观错误码 调用第三方服务出错 ==========
37 | REMOTE_ERROR("C000001", "调用第三方服务出错");
38 |
39 | private final String code;
40 |
41 | private final String message;
42 |
43 | ErrorCode(String code, String message) {
44 | this.code = code;
45 | this.message = message;
46 | }
47 |
48 | @Override
49 | public String code() {
50 | return code;
51 | }
52 |
53 | @Override
54 | public String message() {
55 | return message;
56 | }
57 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/common/convention/errorcode/IErrorCode.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.common.convention.errorcode;
2 |
3 | /**
4 | * 平台错误码
5 | *
6 | * @author Enndfp
7 | */
8 | public interface IErrorCode {
9 |
10 | /**
11 | * 错误码
12 | *
13 | * @return 错误码
14 | */
15 | String code();
16 |
17 | /**
18 | * 错误信息
19 | *
20 | * @return 错误信息
21 | */
22 | String message();
23 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/common/convention/exception/AbstractException.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.common.convention.exception;
2 |
3 | import com.enndfp.shortlink.admin.common.convention.errorcode.IErrorCode;
4 | import lombok.Getter;
5 | import org.springframework.util.StringUtils;
6 |
7 | import java.util.Optional;
8 |
9 | /**
10 | * 抽象项目中三类异常体系,客户端异常、服务端异常以及远程服务调用异常
11 | *
12 | * @author Enndfp
13 | * @see ClientException
14 | * @see ServerException
15 | * @see RemoteException
16 | */
17 | @Getter
18 | public abstract class AbstractException extends RuntimeException {
19 |
20 | public final String errorCode;
21 |
22 | public final String errorMessage;
23 |
24 | public AbstractException(String message, Throwable throwable, IErrorCode errorCode) {
25 | super(message, throwable);
26 | this.errorCode = errorCode.code();
27 | this.errorMessage = Optional.ofNullable(StringUtils.hasLength(message) ? message : null).orElse(errorCode.message());
28 | }
29 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/common/convention/exception/ClientException.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.common.convention.exception;
2 |
3 | import com.enndfp.shortlink.admin.common.convention.errorcode.ErrorCode;
4 | import com.enndfp.shortlink.admin.common.convention.errorcode.IErrorCode;
5 |
6 | /**
7 | * 客户端异常
8 | *
9 | * @author Enndfp
10 | */
11 | public class ClientException extends AbstractException {
12 |
13 | public ClientException(IErrorCode errorCode) {
14 | this(null, null, errorCode);
15 | }
16 |
17 | public ClientException(IErrorCode errorCode,String message) {
18 | this(message, null, errorCode);
19 | }
20 |
21 | public ClientException(String message, Throwable throwable, IErrorCode errorCode) {
22 | super(message, throwable, errorCode);
23 | }
24 |
25 | @Override
26 | public String toString() {
27 | return "ClientException{" +
28 | "code='" + errorCode + "'," +
29 | "message='" + errorMessage + "'" +
30 | '}';
31 | }
32 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/common/convention/exception/GlobalExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.common.convention.exception;
2 |
3 | import com.enndfp.shortlink.admin.common.convention.errorcode.ErrorCode;
4 | import com.enndfp.shortlink.admin.common.convention.result.Result;
5 | import com.enndfp.shortlink.admin.utils.ResultUtil;
6 | import lombok.SneakyThrows;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.springframework.validation.BindingResult;
9 | import org.springframework.validation.FieldError;
10 | import org.springframework.web.bind.MethodArgumentNotValidException;
11 | import org.springframework.web.bind.annotation.ExceptionHandler;
12 | import org.springframework.web.bind.annotation.RestControllerAdvice;
13 |
14 | import java.util.HashMap;
15 | import java.util.List;
16 | import java.util.Map;
17 |
18 | /**
19 | * 全局异常处理器
20 | *
21 | * @author Enndfp
22 | */
23 | @Slf4j
24 | @RestControllerAdvice
25 | public class GlobalExceptionHandler {
26 |
27 | /**
28 | * 拦截参数验证异常
29 | */
30 | @SneakyThrows
31 | @ExceptionHandler(MethodArgumentNotValidException.class)
32 | public Result> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex) {
33 | log.error("methodArgumentNotValidExceptionHandler: ", ex);
34 | BindingResult bindingResult = ex.getBindingResult();
35 | Map errorMap = getErrors(bindingResult);
36 | return ResultUtil.failure(ErrorCode.SERVICE_ERROR, errorMap);
37 | }
38 |
39 | /**
40 | * 拦截自定义抛出的异常
41 | */
42 | @ExceptionHandler(AbstractException.class)
43 | public Result> abstractException(AbstractException ex) {
44 | log.error("abstractException: " + ex.getMessage(), ex);
45 | return ResultUtil.failure(ex.getErrorCode(), ex.getMessage());
46 | }
47 |
48 | /**
49 | * 运行时异常捕获
50 | */
51 | @ExceptionHandler(RuntimeException.class)
52 | public Result> runtimeExceptionHandler(RuntimeException ex) {
53 | log.error("runtimeException: ", ex);
54 | return ResultUtil.failure(ErrorCode.SERVICE_ERROR, ex.getMessage());
55 | }
56 |
57 | /**
58 | * 从方法参数校验异常中获取详细异常信息
59 | *
60 | * @param bindingResult
61 | * @return
62 | */
63 | private Map getErrors(BindingResult bindingResult) {
64 |
65 | Map errorMap = new HashMap<>(16);
66 | List errorList = bindingResult.getFieldErrors();
67 |
68 | for (FieldError error : errorList) {
69 | // 错误所对应的属性字段名
70 | String field = error.getField();
71 | // 错误所对应的信息
72 | String message = error.getDefaultMessage();
73 | errorMap.put(field, message);
74 | }
75 | return errorMap;
76 | }
77 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/common/convention/exception/RemoteException.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.common.convention.exception;
2 |
3 | import com.enndfp.shortlink.admin.common.convention.errorcode.ErrorCode;
4 | import com.enndfp.shortlink.admin.common.convention.errorcode.IErrorCode;
5 |
6 | /**
7 | * 远程服务调用异常
8 | *
9 | * @author Enndfp
10 | */
11 | public class RemoteException extends AbstractException {
12 |
13 | public RemoteException(IErrorCode errorCode) {
14 | this(null, null, errorCode);
15 | }
16 |
17 | public RemoteException(IErrorCode errorCode,String message) {
18 | this(message, null, errorCode);
19 | }
20 |
21 | public RemoteException(String message, Throwable throwable, IErrorCode errorCode) {
22 | super(message, throwable, errorCode);
23 | }
24 |
25 | @Override
26 | public String toString() {
27 | return "RemoteException{" +
28 | "code='" + errorCode + "'," +
29 | "message='" + errorMessage + "'" +
30 | '}';
31 | }
32 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/common/convention/exception/ServerException.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.common.convention.exception;
2 |
3 | import com.enndfp.shortlink.admin.common.convention.errorcode.ErrorCode;
4 | import com.enndfp.shortlink.admin.common.convention.errorcode.IErrorCode;
5 |
6 | import java.util.Optional;
7 |
8 | /**
9 | * 服务端异常
10 | *
11 | * @author Enndfp
12 | */
13 | public class ServerException extends AbstractException {
14 |
15 | public ServerException(IErrorCode errorCode,String message) {
16 | this(message, null, errorCode);
17 | }
18 |
19 | public ServerException(IErrorCode errorCode) {
20 | this(null, errorCode);
21 | }
22 |
23 | public ServerException(String message, IErrorCode errorCode) {
24 | this(message, null, errorCode);
25 | }
26 |
27 | public ServerException(String message, Throwable throwable, IErrorCode errorCode) {
28 | super(Optional.ofNullable(message).orElse(errorCode.message()), throwable, errorCode);
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return "ServerException{" +
34 | "code='" + errorCode + "'," +
35 | "message='" + errorMessage + "'" +
36 | '}';
37 | }
38 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/common/convention/result/Result.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.common.convention.result;
2 |
3 | import com.enndfp.shortlink.admin.common.convention.errorcode.IErrorCode;
4 | import lombok.Data;
5 | import lombok.experimental.Accessors;
6 |
7 | import java.io.Serial;
8 | import java.io.Serializable;
9 |
10 | /**
11 | * 全局返回对象
12 | *
13 | * @author Enndfp
14 | */
15 | @Data
16 | @Accessors(chain = true)
17 | public class Result implements Serializable {
18 |
19 | @Serial
20 | private static final long serialVersionUID = 5679018624309023727L;
21 |
22 | /**
23 | * 返回码
24 | */
25 | private String code;
26 |
27 | /**
28 | * 返回消息
29 | */
30 | private String message;
31 |
32 | /**
33 | * 响应数据
34 | */
35 | private T data;
36 |
37 | /**
38 | * 请求ID
39 | */
40 | private String requestId;
41 |
42 | public Result(String code, String message, T data, String requestId) {
43 | this.code = code;
44 | this.message = message;
45 | this.data = data;
46 | this.requestId = requestId;
47 | }
48 |
49 | public Result(IErrorCode iErrorCode) {
50 | this(iErrorCode.code(), iErrorCode.message(), null, null);
51 | }
52 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/common/serialize/PhoneDesensitizationSerializer.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.common.serialize;
2 |
3 | import cn.hutool.core.util.DesensitizedUtil;
4 | import com.fasterxml.jackson.core.JsonGenerator;
5 | import com.fasterxml.jackson.databind.JsonSerializer;
6 | import com.fasterxml.jackson.databind.SerializerProvider;
7 |
8 | import java.io.IOException;
9 |
10 | /**
11 | * 手机号脱敏反序列化
12 | *
13 | * @author Enndfp
14 | */
15 | public class PhoneDesensitizationSerializer extends JsonSerializer {
16 |
17 | @Override
18 | public void serialize(String phone, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
19 | String phoneDesensitization = DesensitizedUtil.mobilePhone(phone);
20 | jsonGenerator.writeString(phoneDesensitization);
21 | }
22 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/config/InterceptorConfig.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.config;
2 |
3 | import com.enndfp.shortlink.admin.interceptor.UserContextInterceptor;
4 | import jakarta.annotation.Resource;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.data.redis.core.StringRedisTemplate;
7 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
9 |
10 | import java.util.List;
11 |
12 | /**
13 | * 拦截器配置类
14 | *
15 | * @author Enndfp
16 | */
17 | @Configuration
18 | public class InterceptorConfig implements WebMvcConfigurer {
19 |
20 | @Resource
21 | private StringRedisTemplate stringRedisTemplate;
22 |
23 | private static final List EXCLUDE_PATHS = List.of(
24 | "/user/login",
25 | "/user/register",
26 | "/user/check-username"
27 | );
28 |
29 | @Override
30 | public void addInterceptors(InterceptorRegistry registry) {
31 |
32 | registry.addInterceptor(new UserContextInterceptor(stringRedisTemplate))
33 | .excludePathPatterns(EXCLUDE_PATHS)
34 | .order(0);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/config/MyBatisPlusConfig.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.config;
2 |
3 | import com.baomidou.mybatisplus.annotation.DbType;
4 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
5 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 |
9 | /**
10 | * MyBatis Plus 配置
11 | *
12 | * @author Enndfp
13 | */
14 | @Configuration
15 | public class MyBatisPlusConfig {
16 |
17 | /**
18 | * 拦截器配置
19 | */
20 | @Bean
21 | public MybatisPlusInterceptor mybatisPlusInterceptor() {
22 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
23 | // 分页插件
24 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
25 | return interceptor;
26 | }
27 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/config/RbloomFilterConfig.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.config;
2 |
3 | import org.redisson.api.RBloomFilter;
4 | import org.redisson.api.RedissonClient;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 |
8 | /**
9 | * 布隆过滤器配置
10 | *
11 | * @author Enndfp
12 | */
13 | @Configuration
14 | public class RbloomFilterConfig {
15 |
16 | /**
17 | * 防止用户注册查询数据库的布隆过滤器
18 | */
19 | @Bean
20 | public RBloomFilter userRegisterCachePenetrationBloomFilter(RedissonClient redissonClient) {
21 | // 1. 创建布隆过滤器对象
22 | RBloomFilter cachePenetrationBloomFilter = redissonClient.getBloomFilter("userRegisterCachePenetrationBloomFilter");
23 | // 2. 初始化布隆过滤器(预计元素数量为100000000L,误差率为0.001)
24 | cachePenetrationBloomFilter.tryInit(100000000L, 0.001);
25 |
26 | return cachePenetrationBloomFilter;
27 | }
28 |
29 | /**
30 | * 防止Gid生成重复查询数据库的布隆过滤器
31 | */
32 | @Bean
33 | public RBloomFilter gidGenerateCachePenetrationBloomFilter(RedissonClient redissonClient) {
34 | // 1. 创建布隆过滤器对象
35 | RBloomFilter cachePenetrationBloomFilter = redissonClient.getBloomFilter("gidGenerateCachePenetrationBloomFilter");
36 | // 2. 初始化布隆过滤器(预计元素数量为100000000L,误差率为0.001)
37 | cachePenetrationBloomFilter.tryInit(100000000L, 0.001);
38 |
39 | return cachePenetrationBloomFilter;
40 | }
41 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/context/UserContext.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.context;
2 |
3 | import com.alibaba.ttl.TransmittableThreadLocal;
4 |
5 | import java.util.Optional;
6 |
7 | /**
8 | * 用户上下文
9 | *
10 | * @author Enndfp
11 | */
12 | public final class UserContext {
13 |
14 | private static final ThreadLocal USER_THREAD_LOCAL = new TransmittableThreadLocal<>();
15 |
16 | /**
17 | * 设置用户至上下文
18 | *
19 | * @param user 用户详情信息
20 | */
21 | public static void setUser(UserContextDTO user) {
22 | USER_THREAD_LOCAL.set(user);
23 | }
24 |
25 | /**
26 | * 获取上下文中用户 ID
27 | *
28 | * @return 用户 ID
29 | */
30 | public static String getUserId() {
31 | UserContextDTO userContextDTO = USER_THREAD_LOCAL.get();
32 | return Optional.ofNullable(userContextDTO).map(UserContextDTO::getId).orElse(null);
33 | }
34 |
35 | /**
36 | * 获取上下文中用户名称
37 | *
38 | * @return 用户名称
39 | */
40 | public static String getUsername() {
41 | UserContextDTO userContextDTO = USER_THREAD_LOCAL.get();
42 | return Optional.ofNullable(userContextDTO).map(UserContextDTO::getUsername).orElse(null);
43 | }
44 |
45 | /**
46 | * 获取上下文中用户真实姓名
47 | *
48 | * @return 用户真实姓名
49 | */
50 | public static String getRealName() {
51 | UserContextDTO userContextDTO = USER_THREAD_LOCAL.get();
52 | return Optional.ofNullable(userContextDTO).map(UserContextDTO::getRealName).orElse(null);
53 | }
54 |
55 | /**
56 | * 清理用户上下文
57 | */
58 | public static void removeUser() {
59 | USER_THREAD_LOCAL.remove();
60 | }
61 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/context/UserContextDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.context;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 用户上下文信息传输实体
7 | *
8 | * @author Enndfp
9 | */
10 | @Data
11 | public class UserContextDTO {
12 |
13 | /**
14 | * 用户 ID
15 | */
16 | private String id;
17 |
18 | /**
19 | * 用户名
20 | */
21 | private String username;
22 |
23 | /**
24 | * 真实姓名
25 | */
26 | private String realName;
27 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/controller/GroupController.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.controller;
2 |
3 | import cn.hutool.core.util.StrUtil;
4 | import com.enndfp.shortlink.admin.common.convention.errorcode.ErrorCode;
5 | import com.enndfp.shortlink.admin.common.convention.result.Result;
6 | import com.enndfp.shortlink.admin.dto.req.group.GroupAddReqDTO;
7 | import com.enndfp.shortlink.admin.dto.req.group.GroupSortReqDTO;
8 | import com.enndfp.shortlink.admin.dto.req.group.GroupUpdateReqDTO;
9 | import com.enndfp.shortlink.admin.dto.resp.group.GroupRespDTO;
10 | import com.enndfp.shortlink.admin.service.GroupService;
11 | import com.enndfp.shortlink.admin.utils.ResultUtil;
12 | import com.enndfp.shortlink.admin.utils.ThrowUtil;
13 | import jakarta.annotation.Resource;
14 | import org.springframework.web.bind.annotation.*;
15 |
16 | import java.util.List;
17 |
18 | /**
19 | * 短链接分组控制层
20 | *
21 | * @author Enndfp
22 | */
23 | @RestController
24 | @RequestMapping("/group")
25 | public class GroupController {
26 |
27 | @Resource
28 | private GroupService groupService;
29 |
30 | /**
31 | * 添加分组
32 | *
33 | * @param groupAddReqDTO 分组添加请求数据传输对象
34 | * @return 添加结果
35 | */
36 | @PostMapping("/add")
37 | public Result add(@RequestBody GroupAddReqDTO groupAddReqDTO) {
38 | // 1. 校验请求参数
39 | ThrowUtil.throwClientIf(groupAddReqDTO == null, ErrorCode.CLIENT_ERROR);
40 | // 2. 添加分组
41 | groupService.add(groupAddReqDTO);
42 |
43 | return ResultUtil.success();
44 | }
45 |
46 | /**
47 | * 查询分组列表
48 | *
49 | * @return 分组列表
50 | */
51 | @GetMapping("/list")
52 | public Result> list() {
53 | // 1. 查询分组列表
54 | List groupRespDTOList = groupService.listGroup();
55 |
56 | return ResultUtil.success(groupRespDTOList);
57 | }
58 |
59 | /**
60 | * 修改分组
61 | *
62 | * @param groupUpdateReqDTO 分组修改请求数据传输对象
63 | * @return 修改结果
64 | */
65 | @PostMapping("/update")
66 | public Result update(@RequestBody GroupUpdateReqDTO groupUpdateReqDTO) {
67 | // 1. 校验请求参数
68 | ThrowUtil.throwClientIf(groupUpdateReqDTO == null, ErrorCode.CLIENT_ERROR);
69 | // 2. 修改分组
70 | groupService.update(groupUpdateReqDTO);
71 |
72 | return ResultUtil.success();
73 | }
74 |
75 | /**
76 | * 删除分组
77 | *
78 | * @param gid 分组标识
79 | * @return 删除结果
80 | */
81 | @PostMapping("/delete")
82 | public Result delete(@RequestParam String gid) {
83 | // 1. 校验请求参数
84 | ThrowUtil.throwClientIf(StrUtil.isBlank(gid), ErrorCode.CLIENT_ERROR);
85 | // 2. 删除分组
86 | groupService.delete(gid);
87 |
88 | return ResultUtil.success();
89 | }
90 |
91 | /**
92 | * 排序分组
93 | *
94 | * @param groupSortReqDTOList 分组排序请求数据传输对象列表
95 | * @return 排序结果
96 | */
97 | @PostMapping("sort")
98 | public Result sort(@RequestBody List groupSortReqDTOList) {
99 | // 1. 校验请求参数
100 | ThrowUtil.throwClientIf(groupSortReqDTOList == null, ErrorCode.CLIENT_ERROR);
101 | // 2. 排序分组
102 | groupService.sort(groupSortReqDTOList);
103 |
104 | return ResultUtil.success();
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/controller/LinkController.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.controller;
2 |
3 | import com.baomidou.mybatisplus.core.metadata.IPage;
4 | import com.enndfp.shortlink.admin.common.convention.errorcode.ErrorCode;
5 | import com.enndfp.shortlink.admin.common.convention.result.Result;
6 | import com.enndfp.shortlink.admin.remote.dto.LinkRemoteService;
7 | import com.enndfp.shortlink.admin.remote.dto.req.link.LinkPageReqDTO;
8 | import com.enndfp.shortlink.admin.remote.dto.resp.link.LinkPageRespDTO;
9 | import com.enndfp.shortlink.admin.remote.dto.req.link.LinkCreateReqDTO;
10 | import com.enndfp.shortlink.admin.remote.dto.resp.link.LinkCreateRespDTO;
11 | import com.enndfp.shortlink.admin.utils.ThrowUtil;
12 | import org.springframework.web.bind.annotation.*;
13 |
14 | /**
15 | * 短链接控制层
16 | *
17 | * @author Enndfp
18 | */
19 | @RestController
20 | @RequestMapping("/link")
21 | public class LinkController {
22 |
23 | /**
24 | * TODO: 后续重构为 SpringCloud Feign 调用
25 | */
26 | LinkRemoteService linkRemoteService = new LinkRemoteService() {
27 | };
28 |
29 | /**
30 | * 创建短链接
31 | *
32 | * @param linkCreateReqDTO 短链接创建请求传输对象
33 | * @return 短链接创建响应传输对象
34 | */
35 | @PostMapping("/create")
36 | public Result create(@RequestBody LinkCreateReqDTO linkCreateReqDTO) {
37 | // 1. 校验请求参数
38 | ThrowUtil.throwClientIf(linkCreateReqDTO == null, ErrorCode.CLIENT_ERROR);
39 | // 2. 执行创建短链接逻辑
40 | return linkRemoteService.create(linkCreateReqDTO);
41 | }
42 |
43 | /**
44 | * 分页查询短链接
45 | *
46 | * @param linkPageReqDTO 短链接分页请求传输对象
47 | * @return 短链接分页响应传输对象
48 | */
49 | @GetMapping("/page")
50 | public Result> page(@ModelAttribute LinkPageReqDTO linkPageReqDTO) {
51 | // 1. 校验请求参数
52 | ThrowUtil.throwClientIf(linkPageReqDTO == null, ErrorCode.CLIENT_ERROR);
53 | // 2. 执行分页查询短链接逻辑
54 | return linkRemoteService.pageLink(linkPageReqDTO);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/controller/UserController.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.controller;
2 |
3 | import cn.hutool.core.util.StrUtil;
4 | import com.enndfp.shortlink.admin.common.convention.errorcode.ErrorCode;
5 | import com.enndfp.shortlink.admin.common.convention.result.Result;
6 | import com.enndfp.shortlink.admin.dto.req.user.UserLoginReqDTO;
7 | import com.enndfp.shortlink.admin.dto.req.user.UserRegisterReqDTO;
8 | import com.enndfp.shortlink.admin.dto.req.user.UserUpdateReqDTO;
9 | import com.enndfp.shortlink.admin.dto.resp.user.UserActualRespDTO;
10 | import com.enndfp.shortlink.admin.dto.resp.user.UserLoginRespDTO;
11 | import com.enndfp.shortlink.admin.dto.resp.user.UserRespDTO;
12 | import com.enndfp.shortlink.admin.service.UserService;
13 | import com.enndfp.shortlink.admin.utils.ResultUtil;
14 | import com.enndfp.shortlink.admin.utils.ThrowUtil;
15 | import jakarta.annotation.Resource;
16 | import org.springframework.web.bind.annotation.*;
17 |
18 | /**
19 | * 用户管理控制层
20 | *
21 | * @author Enndfp
22 | */
23 | @RestController
24 | @RequestMapping("/user")
25 | public class UserController {
26 |
27 | @Resource
28 | private UserService userService;
29 |
30 | /**
31 | * 根据用户名查询用户信息
32 | *
33 | * @param username 用户名
34 | * @return 用户信息
35 | */
36 | @GetMapping("/query")
37 | public Result getUserByUsername(@RequestParam("username") String username) {
38 | // 1. 校验请求参数
39 | ThrowUtil.throwClientIf(username == null, ErrorCode.USER_NAME_NULL);
40 | // 2. 查询用户信息
41 | UserRespDTO userRespDTO = userService.getUserByUsername(username);
42 |
43 | return ResultUtil.success(userRespDTO);
44 | }
45 |
46 | /**
47 | * 根据用户名查询无脱敏用户信息
48 | *
49 | * @param username 用户名
50 | * @return 用户信息
51 | */
52 | @GetMapping("/query-actual")
53 | public Result getActualUserByUsername(@RequestParam("username") String username) {
54 | // 1. 校验请求参数
55 | ThrowUtil.throwClientIf(username == null, ErrorCode.USER_NAME_NULL);
56 | // 2. 查询用户信息
57 | UserActualRespDTO userActualRespDTO = userService.getActualUserByUsername(username);
58 |
59 | return ResultUtil.success(userActualRespDTO);
60 | }
61 |
62 | /**
63 | * 根据用户名判断用户是否存在
64 | *
65 | * @param username 用户名
66 | * @return 是否存在
67 | */
68 | @GetMapping("/check-username")
69 | public Result checkUserExistByUsername(@RequestParam("username") String username) {
70 | // 1. 校验请求参数
71 | ThrowUtil.throwClientIf(username == null, ErrorCode.USER_NAME_NULL);
72 | // 2. 执行检查逻辑
73 | return ResultUtil.success(userService.checkUsername(username));
74 | }
75 |
76 | /**
77 | * 用户注册
78 | *
79 | * @param userRegisterReqDTO 用户注册请求实体
80 | * @return 注册结果
81 | */
82 | @PostMapping("/register")
83 | public Result register(@RequestBody UserRegisterReqDTO userRegisterReqDTO) {
84 | // 1. 校验请求参数
85 | ThrowUtil.throwClientIf(userRegisterReqDTO == null, ErrorCode.CLIENT_ERROR);
86 | // 2. 处理注册逻辑
87 | userService.register(userRegisterReqDTO);
88 |
89 | return ResultUtil.success();
90 | }
91 |
92 | /**
93 | * 用户修改
94 | *
95 | * @param userUpdateReqDTO 用户修改请求实体
96 | * @return 修改结果
97 | */
98 | @PostMapping("/update")
99 | public Result update(@RequestBody UserUpdateReqDTO userUpdateReqDTO) {
100 | // 1. 校验请求参数
101 | ThrowUtil.throwClientIf(userUpdateReqDTO == null, ErrorCode.CLIENT_ERROR);
102 | // 2. 处理修改逻辑
103 | userService.update(userUpdateReqDTO);
104 |
105 | return ResultUtil.success();
106 | }
107 |
108 | /**
109 | * 用户登录
110 | *
111 | * @param userLoginReqDTO 用户登录请求实体
112 | * @return 登录结果
113 | */
114 | @PostMapping("/login")
115 | public Result login(@RequestBody UserLoginReqDTO userLoginReqDTO) {
116 | // 1. 校验请求参数
117 | ThrowUtil.throwClientIf(userLoginReqDTO == null, ErrorCode.CLIENT_ERROR);
118 | // 2. 处理登录逻辑
119 | UserLoginRespDTO userLoginRespDTO = userService.login(userLoginReqDTO);
120 |
121 | return ResultUtil.success(userLoginRespDTO);
122 | }
123 |
124 | /**
125 | * 检查用户是否登录
126 | *
127 | * @param username 用户名
128 | * @param token token
129 | * @return 是否登录
130 | */
131 | @GetMapping("/check-login")
132 | public Result checkLogin(@RequestParam("username") String username, @RequestParam("token") String token) {
133 | // 1. 校验请求参数
134 | ThrowUtil.throwClientIf(StrUtil.hasBlank(username, token), ErrorCode.USER_OR_TOKEN_NULL);
135 | // 2. 执行检查逻辑
136 | Boolean checkLogin = userService.checkLogin(username, token);
137 |
138 | return ResultUtil.success(checkLogin);
139 | }
140 |
141 | /**
142 | * 用户登出
143 | *
144 | * @param username 用户名
145 | * @param token token
146 | * @return 登出结果
147 | */
148 | @PostMapping("/logout")
149 | public Result logout(@RequestParam("username") String username, @RequestParam("token") String token) {
150 | // 1. 校验请求参数
151 | ThrowUtil.throwClientIf(StrUtil.hasBlank(username, token), ErrorCode.USER_OR_TOKEN_NULL);
152 | // 2. 处理登出逻辑
153 | userService.logout(username, token);
154 |
155 | return ResultUtil.success();
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dao/entity/GroupDO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dao.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.*;
4 | import lombok.Data;
5 |
6 | import java.io.Serial;
7 | import java.io.Serializable;
8 | import java.util.Date;
9 |
10 | /**
11 | * 短链接分组持久层实体类
12 | *
13 | * @author Enndfp
14 | */
15 | @Data
16 | @TableName(value = "t_group")
17 | public class GroupDO implements Serializable {
18 | /**
19 | * ID
20 | */
21 | @TableId(type = IdType.ASSIGN_ID)
22 | private Long id;
23 |
24 | /**
25 | * 分组标识
26 | */
27 | private String gid;
28 |
29 | /**
30 | * 分组名称
31 | */
32 | private String groupName;
33 |
34 | /**
35 | * 创建分组用户名
36 | */
37 | private String username;
38 |
39 | /**
40 | * 分组排序
41 | */
42 | private Integer sortOrder;
43 |
44 | /**
45 | * 创建时间
46 | */
47 | private Date createdTime;
48 |
49 | /**
50 | * 修改时间
51 | */
52 | private Date updatedTime;
53 |
54 | /**
55 | * 删除标识 0:未删除 1:已删除
56 | */
57 | @TableLogic
58 | private Integer isDeleted;
59 |
60 | @Serial
61 | @TableField(exist = false)
62 | private static final long serialVersionUID = 1L;
63 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dao/entity/UserDO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dao.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.*;
4 | import com.enndfp.shortlink.admin.common.serialize.PhoneDesensitizationSerializer;
5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize;
6 | import lombok.Data;
7 |
8 | import java.io.Serial;
9 | import java.io.Serializable;
10 | import java.util.Date;
11 |
12 | /**
13 | * 用户持久层实体类
14 | *
15 | * @author Enndfp
16 | */
17 | @Data
18 | @TableName("t_user")
19 | public class UserDO implements Serializable {
20 |
21 | /**
22 | * ID
23 | */
24 | @TableId(type = IdType.ASSIGN_ID)
25 | private Long id;
26 |
27 | /**
28 | * 用户名
29 | */
30 | private String username;
31 |
32 | /**
33 | * 密码
34 | */
35 | private String password;
36 |
37 | /**
38 | * 真实姓名
39 | */
40 | private String realName;
41 |
42 | /**
43 | * 手机号
44 | */
45 | @JsonSerialize(using = PhoneDesensitizationSerializer.class)
46 | private String phone;
47 |
48 | /**
49 | * 邮箱
50 | */
51 | private String mail;
52 |
53 | /**
54 | * 注销时间戳
55 | */
56 | private Date deletedTime;
57 |
58 | /**
59 | * 创建时间
60 | */
61 | private Date createdTime;
62 |
63 | /**
64 | * 修改时间
65 | */
66 | private Date updatedTime;
67 |
68 | /**
69 | * 删除标识 0:未删除 1:已删除
70 | */
71 | @TableLogic(value = "0", delval = "1,deleted_time = now()")
72 | private Integer isDeleted;
73 |
74 | @Serial
75 | @TableField(exist = false)
76 | private static final long serialVersionUID = 1L;
77 |
78 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dao/mapper/GroupMapper.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dao.mapper;
2 |
3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4 | import com.enndfp.shortlink.admin.dao.entity.GroupDO;
5 |
6 | /**
7 | * 短链接分组持久层接口
8 | *
9 | * @author Enndfp
10 | */
11 | public interface GroupMapper extends BaseMapper {
12 |
13 | }
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dao/mapper/UserMapper.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dao.mapper;
2 |
3 |
4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 | import com.enndfp.shortlink.admin.dao.entity.UserDO;
6 |
7 | /**
8 | * 用户持久层接口
9 | *
10 | * @author Enndfp
11 | */
12 | public interface UserMapper extends BaseMapper {
13 | }
14 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dto/req/group/GroupAddReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dto.req.group;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 短链接分组添加请求数据传输对象
7 | *
8 | * @author Enndfp
9 | */
10 | @Data
11 | public class GroupAddReqDTO {
12 |
13 | /**
14 | * 分组名称
15 | */
16 | private String groupName;
17 | }
18 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dto/req/group/GroupSortReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dto.req.group;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 短链接分组排序请求数据传输对象
7 | *
8 | * @author Enndfp
9 | */
10 | @Data
11 | public class GroupSortReqDTO {
12 |
13 | /**
14 | * 分组标识
15 | */
16 | private String gid;
17 |
18 | /**
19 | * 排序
20 | */
21 | private Integer sortOrder;
22 | }
23 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dto/req/group/GroupUpdateReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dto.req.group;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 短链接分组修改请求数据传输对象
7 | *
8 | * @author Enndfp
9 | */
10 | @Data
11 | public class GroupUpdateReqDTO {
12 |
13 | /**
14 | * 分组标识
15 | */
16 | private String gid;
17 |
18 | /**
19 | * 分组名称
20 | */
21 | private String groupName;
22 | }
23 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dto/req/user/UserLoginReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dto.req.user;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 用户登录请求实体
7 | *
8 | * @author Enndfp
9 | */
10 | @Data
11 | public class UserLoginReqDTO {
12 |
13 | /**
14 | * 用户名
15 | */
16 | private String username;
17 |
18 | /**
19 | * 密码
20 | */
21 | private String password;
22 | }
23 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dto/req/user/UserRegisterReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dto.req.user;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 用户注册请求实体
7 | *
8 | * @author Enndfp
9 | */
10 | @Data
11 | public class UserRegisterReqDTO {
12 |
13 | /**
14 | * 用户名
15 | */
16 | private String username;
17 |
18 | /**
19 | * 密码
20 | */
21 | private String password;
22 |
23 | /**
24 | * 真实姓名
25 | */
26 | private String realName;
27 |
28 | /**
29 | * 手机号
30 | */
31 | private String phone;
32 |
33 | /**
34 | * 邮箱
35 | */
36 | private String mail;
37 | }
38 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dto/req/user/UserUpdateReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dto.req.user;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 用户修改请求实体
7 | *
8 | * @author Enndfp
9 | */
10 | @Data
11 | public class UserUpdateReqDTO {
12 |
13 | /**
14 | * 用户名
15 | */
16 | private String username;
17 |
18 | /**
19 | * 密码
20 | */
21 | private String password;
22 |
23 | /**
24 | * 真实姓名
25 | */
26 | private String realName;
27 |
28 | /**
29 | * 手机号
30 | */
31 | private String phone;
32 |
33 | /**
34 | * 邮箱
35 | */
36 | private String mail;
37 | }
38 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dto/resp/group/GroupRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dto.resp.group;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 分组信息返回实体
7 | *
8 | * @author Enndfp
9 | */
10 | @Data
11 | public class GroupRespDTO {
12 |
13 | /**
14 | * 分组标识
15 | */
16 | private String gid;
17 |
18 | /**
19 | * 分组名称
20 | */
21 | private String groupName;
22 |
23 | /**
24 | * 分组排序
25 | */
26 | private Integer sortOrder;
27 |
28 | /**
29 | * 分组下的链接数量
30 | */
31 | private Integer linkCount;
32 | }
33 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dto/resp/user/UserActualRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dto.resp.user;
2 |
3 | import com.enndfp.shortlink.admin.common.serialize.PhoneDesensitizationSerializer;
4 | import com.fasterxml.jackson.databind.annotation.JsonSerialize;
5 | import lombok.Data;
6 |
7 | /**
8 | * 用户信息返回实体
9 | *
10 | * @author Enndfp
11 | */
12 | @Data
13 | public class UserActualRespDTO {
14 |
15 | private static final long serialVersionUID = 1L;
16 |
17 | /**
18 | * id
19 | */
20 | private Long id;
21 |
22 | /**
23 | * 用户名
24 | */
25 | private String username;
26 |
27 | /**
28 | * 真实姓名
29 | */
30 | private String realName;
31 |
32 | /**
33 | * 手机号
34 | */
35 | private String phone;
36 |
37 | /**
38 | * 邮箱
39 | */
40 | private String mail;
41 | }
42 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dto/resp/user/UserLoginRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dto.resp.user;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | /**
8 | * 用户登录返回实体
9 | *
10 | * @author Enndfp
11 | */
12 | @Data
13 | @NoArgsConstructor
14 | @AllArgsConstructor
15 | public class UserLoginRespDTO {
16 |
17 | private static final long serialVersionUID = 1L;
18 |
19 | /**
20 | * 用户token
21 | */
22 | private String token;
23 | }
24 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/dto/resp/user/UserRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.dto.resp.user;
2 |
3 | import com.enndfp.shortlink.admin.common.serialize.PhoneDesensitizationSerializer;
4 | import com.fasterxml.jackson.databind.annotation.JsonSerialize;
5 | import lombok.Data;
6 |
7 | /**
8 | * 用户信息返回实体
9 | *
10 | * @author Enndfp
11 | */
12 | @Data
13 | public class UserRespDTO {
14 |
15 | private static final long serialVersionUID = 1L;
16 |
17 | /**
18 | * id
19 | */
20 | private Long id;
21 |
22 | /**
23 | * 用户名
24 | */
25 | private String username;
26 |
27 | /**
28 | * 真实姓名
29 | */
30 | private String realName;
31 |
32 | /**
33 | * 手机号
34 | */
35 | @JsonSerialize(using = PhoneDesensitizationSerializer.class)
36 | private String phone;
37 |
38 | /**
39 | * 邮箱
40 | */
41 | private String mail;
42 | }
43 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/interceptor/UserContextInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.interceptor;
2 |
3 | import cn.hutool.core.util.StrUtil;
4 | import cn.hutool.json.JSONUtil;
5 | import com.enndfp.shortlink.admin.common.convention.errorcode.ErrorCode;
6 | import com.enndfp.shortlink.admin.context.UserContext;
7 | import com.enndfp.shortlink.admin.context.UserContextDTO;
8 | import com.enndfp.shortlink.admin.utils.ThrowUtil;
9 | import jakarta.servlet.http.HttpServletRequest;
10 | import jakarta.servlet.http.HttpServletResponse;
11 | import lombok.RequiredArgsConstructor;
12 | import org.springframework.data.redis.core.StringRedisTemplate;
13 | import org.springframework.web.servlet.HandlerInterceptor;
14 |
15 | import static com.enndfp.shortlink.admin.common.constant.RedisCacheConstant.USER_LOGIN_KEY;
16 |
17 | /**
18 | * 用户上下文信息传递拦截器
19 | *
20 | * @author Enndfp
21 | */
22 | @RequiredArgsConstructor
23 | public class UserContextInterceptor implements HandlerInterceptor {
24 |
25 | private final StringRedisTemplate stringRedisTemplate;
26 |
27 | @Override
28 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
29 | // 1. 从请求头中获取用户名和token
30 | String username = request.getHeader("username");
31 | String token = request.getHeader("token");
32 |
33 | // 2. 校验用户是否登录
34 | String userInfoJsonStr = (String) stringRedisTemplate.opsForHash().get(USER_LOGIN_KEY + username, token);
35 | ThrowUtil.throwServerIf(StrUtil.isEmpty(userInfoJsonStr), ErrorCode.USER_NOT_LOGIN);
36 |
37 | // 3. 将用户信息放入上下文
38 | UserContextDTO userContextDTO = JSONUtil.toBean(userInfoJsonStr, UserContextDTO.class);
39 | UserContext.setUser(userContextDTO);
40 |
41 | return true;
42 | }
43 |
44 | @Override
45 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
46 | // 1. 清除用户上下文信息
47 | UserContext.removeUser();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/remote/dto/LinkRemoteService.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.remote.dto;
2 |
3 | import cn.hutool.http.HttpUtil;
4 | import cn.hutool.json.JSONUtil;
5 | import com.alibaba.fastjson2.JSON;
6 | import com.alibaba.fastjson2.TypeReference;
7 | import com.baomidou.mybatisplus.core.metadata.IPage;
8 | import com.enndfp.shortlink.admin.common.convention.result.Result;
9 | import com.enndfp.shortlink.admin.remote.dto.req.link.LinkPageReqDTO;
10 | import com.enndfp.shortlink.admin.remote.dto.resp.link.LinkPageRespDTO;
11 | import com.enndfp.shortlink.admin.remote.dto.resp.link.LinkCountRespDTO;
12 | import com.enndfp.shortlink.admin.remote.dto.req.link.LinkCreateReqDTO;
13 | import com.enndfp.shortlink.admin.remote.dto.resp.link.LinkCreateRespDTO;
14 | import org.springframework.web.bind.annotation.ModelAttribute;
15 | import org.springframework.web.bind.annotation.RequestParam;
16 |
17 | import java.util.HashMap;
18 | import java.util.List;
19 | import java.util.Map;
20 |
21 | /**
22 | * 短链接中台远程调用服务
23 | *
24 | * @author Enndfp
25 | */
26 | public interface LinkRemoteService {
27 |
28 | /**
29 | * 创建短链接
30 | *
31 | * @param linkCreateReqDTO 短链接创建请求传输对象
32 | * @return 短链接创建响应传输对象
33 | */
34 | default Result create(LinkCreateReqDTO linkCreateReqDTO) {
35 | // 1. 执行远程调用
36 | String resultBody = HttpUtil.post("http://127.0.0.1:8001/api/short-link/v1/link/create", JSONUtil.toJsonStr(linkCreateReqDTO));
37 |
38 | // 2. 解析响应结果
39 | return JSON.parseObject(resultBody, new TypeReference>() {
40 | });
41 | }
42 |
43 | /**
44 | * 分页查询短链接
45 | *
46 | * @param linkPageReqDTO 短链接分页请求传输对象
47 | * @return 短链接分页响应传输对象
48 | */
49 | default Result> pageLink(@ModelAttribute LinkPageReqDTO linkPageReqDTO) {
50 | // 1. 获取请求参数
51 | Map requestParamMap = new HashMap<>();
52 | requestParamMap.put("gid", linkPageReqDTO.getGid());
53 | requestParamMap.put("current", linkPageReqDTO.getCurrent());
54 | requestParamMap.put("size", linkPageReqDTO.getSize());
55 |
56 | // 2. 执行远程调用
57 | String resultPageStr = HttpUtil.get("http://127.0.0.1:8001/api/short-link/v1/link/page", requestParamMap);
58 |
59 | // 3. 解析响应结果
60 | return JSON.parseObject(resultPageStr, new TypeReference<>() {
61 | });
62 | }
63 |
64 | /**
65 | * 根据分组标识统计链接数量
66 | *
67 | * @param gids 分组标识列表
68 | * @return 链接数量返回数据传输对象列表
69 | */
70 | default Result> count(@RequestParam List gids) {
71 | // 1. 获取请求参数
72 | Map requestParamMap = new HashMap<>();
73 | requestParamMap.put("gids", gids);
74 |
75 | // 2. 执行远程调用
76 | String resultPageStr = HttpUtil.get("http://127.0.0.1:8001/api/short-link/v1/link/count", requestParamMap);
77 |
78 | // 3. 解析响应结果
79 | return JSON.parseObject(resultPageStr, new TypeReference<>() {
80 | });
81 | }
82 |
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/remote/dto/req/link/LinkCreateReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.remote.dto.req.link;
2 |
3 | import com.fasterxml.jackson.annotation.JsonFormat;
4 | import lombok.Data;
5 |
6 | import java.util.Date;
7 |
8 | /**
9 | * 短链接创建请求传输对象
10 | *
11 | * @author Enndfp
12 | */
13 | @Data
14 | public class LinkCreateReqDTO {
15 |
16 | /**
17 | * 域名
18 | */
19 | private String domain;
20 |
21 | /**
22 | * 原始链接
23 | */
24 | private String originUrl;
25 |
26 | /**
27 | * 分组标识
28 | */
29 | private String gid;
30 |
31 | /**
32 | * 创建类型 0:控制台 1:接口
33 | */
34 | private Integer createdType;
35 |
36 | /**
37 | * 有效期类型 0:永久有效 1:用户自定义
38 | */
39 | private Integer validDateType;
40 |
41 | /**
42 | * 有效期
43 | */
44 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
45 | private Date validDate;
46 |
47 | /**
48 | * 描述
49 | */
50 | private String description;
51 | }
52 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/remote/dto/req/link/LinkPageReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.remote.dto.req.link;
2 |
3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4 | import lombok.Data;
5 |
6 | /**
7 | * 短链接分页请求传输对象
8 | *
9 | * @author Enndfp
10 | */
11 | @Data
12 | public class LinkPageReqDTO extends Page {
13 |
14 | /**
15 | * 分组标识
16 | */
17 | private String gid;
18 | }
19 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/remote/dto/resp/link/LinkCountRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.remote.dto.resp.link;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 链接数量返回数据传输对象
7 | *
8 | * @author Enndfp
9 | */
10 | @Data
11 | public class LinkCountRespDTO {
12 |
13 | /**
14 | * 分组标识
15 | */
16 | private String gid;
17 |
18 | /**
19 | * 分组下的链接数量
20 | */
21 | private Integer linkCount;
22 | }
23 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/remote/dto/resp/link/LinkCreateRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.remote.dto.resp.link;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 短链接创建响应传输对象
7 | *
8 | * @author Enndfp
9 | */
10 | @Data
11 | public class LinkCreateRespDTO {
12 |
13 | /**
14 | * 分组标识
15 | */
16 | private String gid;
17 |
18 | /**
19 | * 完整短链接
20 | */
21 | private String fullShortUrl;
22 |
23 | /**
24 | * 原始链接
25 | */
26 | private String originUrl;
27 | }
28 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/remote/dto/resp/link/LinkPageRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.remote.dto.resp.link;
2 |
3 | import com.fasterxml.jackson.annotation.JsonFormat;
4 | import lombok.Data;
5 |
6 | import java.util.Date;
7 |
8 | /**
9 | * 短链接分页响应传输对象
10 | *
11 | * @author Enndfp
12 | */
13 | @Data
14 | public class LinkPageRespDTO {
15 |
16 | /**
17 | * ID
18 | */
19 | private Long id;
20 |
21 | /**
22 | * 域名
23 | */
24 | private String domain;
25 |
26 | /**
27 | * 短链接
28 | */
29 | private String shortUri;
30 |
31 | /**
32 | * 完整短链接
33 | */
34 | private String fullShortUrl;
35 |
36 | /**
37 | * 原始链接
38 | */
39 | private String originUrl;
40 |
41 | /**
42 | * 分组标识
43 | */
44 | private String gid;
45 |
46 | /**
47 | * 网站图标
48 | */
49 | private String favicon;
50 |
51 | /**
52 | * 有效期类型 0:永久有效 1:用户自定义
53 | */
54 | private Integer validDateType;
55 |
56 | /**
57 | * 有效期
58 | */
59 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
60 | private Date validDate;
61 |
62 | /**
63 | * 描述
64 | */
65 | private String description;
66 |
67 | /**
68 | * 创建时间
69 | */
70 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
71 | private Date createdTime;
72 | }
73 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/service/GroupService.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.service;
2 |
3 | import com.baomidou.mybatisplus.extension.service.IService;
4 | import com.enndfp.shortlink.admin.dao.entity.GroupDO;
5 | import com.enndfp.shortlink.admin.dto.req.group.GroupAddReqDTO;
6 | import com.enndfp.shortlink.admin.dto.req.group.GroupSortReqDTO;
7 | import com.enndfp.shortlink.admin.dto.req.group.GroupUpdateReqDTO;
8 | import com.enndfp.shortlink.admin.dto.resp.group.GroupRespDTO;
9 |
10 | import java.util.List;
11 |
12 | /**
13 | * 短链接分组业务层接口
14 | *
15 | * @author Enndfp
16 | */
17 | public interface GroupService extends IService {
18 |
19 | /**
20 | * 添加分组
21 | *
22 | * @param groupAddReqDTO 分组添加请求数据传输对象
23 | */
24 | void add(GroupAddReqDTO groupAddReqDTO);
25 |
26 | /**
27 | * 查询分组列表
28 | *
29 | * @return 分组列表
30 | */
31 | List listGroup();
32 |
33 | /**
34 | * 修改分组
35 | *
36 | * @param groupUpdateReqDTO 分组修改请求数据传输对象
37 | */
38 | void update(GroupUpdateReqDTO groupUpdateReqDTO);
39 |
40 | /**
41 | * 删除分组
42 | *
43 | * @param gid 分组标识
44 | */
45 | void delete(String gid);
46 |
47 | /**
48 | * 分组排序
49 | *
50 | * @param groupSortReqDTOList 分组排序请求数据传输对象列表
51 | */
52 | void sort(List groupSortReqDTOList);
53 |
54 | /**
55 | * 添加默认分组
56 | *
57 | * @param username 用户名
58 | */
59 | void addDefaultGroup(String username);
60 | }
61 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/service/UserService.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.service;
2 |
3 | import com.baomidou.mybatisplus.extension.service.IService;
4 | import com.enndfp.shortlink.admin.dao.entity.UserDO;
5 | import com.enndfp.shortlink.admin.dto.req.user.UserLoginReqDTO;
6 | import com.enndfp.shortlink.admin.dto.req.user.UserRegisterReqDTO;
7 | import com.enndfp.shortlink.admin.dto.req.user.UserUpdateReqDTO;
8 | import com.enndfp.shortlink.admin.dto.resp.user.UserActualRespDTO;
9 | import com.enndfp.shortlink.admin.dto.resp.user.UserLoginRespDTO;
10 | import com.enndfp.shortlink.admin.dto.resp.user.UserRespDTO;
11 |
12 | /**
13 | * 用户业务层接口
14 | *
15 | * @author Enndfp
16 | */
17 | public interface UserService extends IService {
18 |
19 | /**
20 | * 根据用户名获取用户信息
21 | *
22 | * @param username 用户名
23 | * @return 用户信息
24 | */
25 | UserRespDTO getUserByUsername(String username);
26 |
27 | /**
28 | * 根据用户名获取无脱敏用户信息
29 | *
30 | * @param username 用户名
31 | * @return 无脱敏用户信息
32 | */
33 | UserActualRespDTO getActualUserByUsername(String username);
34 |
35 | /**
36 | * 根据用户名判断用户是否存在
37 | *
38 | * @param username 用户名
39 | * @return 是否存在
40 | */
41 | Boolean checkUsername(String username);
42 |
43 | /**
44 | * 用户注册
45 | *
46 | * @param userRegisterReqDTO 用户注册请求实体
47 | */
48 | void register(UserRegisterReqDTO userRegisterReqDTO);
49 |
50 | /**
51 | * 用户修改
52 | *
53 | * @param userUpdateReqDTO 用户修改请求实体
54 | */
55 | void update(UserUpdateReqDTO userUpdateReqDTO);
56 |
57 | /**
58 | * 用户登录
59 | *
60 | * @param userLoginReqDTO 用户登录请求实体
61 | * @return 登录结果
62 | */
63 | UserLoginRespDTO login(UserLoginReqDTO userLoginReqDTO);
64 |
65 | /**
66 | * 检查用户是否登录
67 | *
68 | * @param username 用户名
69 | * @param token token
70 | * @return 是否登录
71 | */
72 | Boolean checkLogin(String username, String token);
73 |
74 | /**
75 | * 用户登出
76 | *
77 | * @param username 用户名
78 | * @param token token
79 | */
80 | void logout(String username, String token);
81 | }
82 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/service/impl/GroupServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.service.impl;
2 |
3 | import cn.hutool.core.bean.BeanUtil;
4 | import cn.hutool.core.util.StrUtil;
5 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
6 | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
7 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
8 | import com.enndfp.shortlink.admin.common.convention.errorcode.ErrorCode;
9 | import com.enndfp.shortlink.admin.context.UserContext;
10 | import com.enndfp.shortlink.admin.dao.entity.GroupDO;
11 | import com.enndfp.shortlink.admin.dao.mapper.GroupMapper;
12 | import com.enndfp.shortlink.admin.dto.req.group.GroupAddReqDTO;
13 | import com.enndfp.shortlink.admin.dto.req.group.GroupSortReqDTO;
14 | import com.enndfp.shortlink.admin.dto.req.group.GroupUpdateReqDTO;
15 | import com.enndfp.shortlink.admin.dto.resp.group.GroupRespDTO;
16 | import com.enndfp.shortlink.admin.remote.dto.LinkRemoteService;
17 | import com.enndfp.shortlink.admin.remote.dto.resp.link.LinkCountRespDTO;
18 | import com.enndfp.shortlink.admin.service.GroupService;
19 | import com.enndfp.shortlink.admin.utils.RandomStringUtil;
20 | import com.enndfp.shortlink.admin.utils.ThrowUtil;
21 | import jakarta.annotation.Resource;
22 | import org.redisson.api.RBloomFilter;
23 | import org.springframework.stereotype.Service;
24 |
25 | import java.util.List;
26 |
27 | /**
28 | * 短链接分组业务层实现类
29 | *
30 | * @author Enndfp
31 | */
32 | @Service
33 | public class GroupServiceImpl extends ServiceImpl implements GroupService {
34 |
35 | @Resource
36 | private GroupMapper groupMapper;
37 |
38 | @Resource
39 | private RBloomFilter gidGenerateCachePenetrationBloomFilter;
40 |
41 | /**
42 | * TODO: 后续重构为 SpringCloud Feign 调用
43 | */
44 | LinkRemoteService linkRemoteService = new LinkRemoteService() {
45 | };
46 |
47 | @Override
48 | public void add(GroupAddReqDTO groupAddReqDTO) {
49 | // 1. 校验请求参数
50 | String groupName = groupAddReqDTO.getGroupName();
51 | ThrowUtil.throwClientIf(StrUtil.isBlank(groupName), ErrorCode.GROUP_NAME_NULL);
52 |
53 | // 2. 生成的分组id不能重复
54 | String gid;
55 | do {
56 | gid = RandomStringUtil.generateRandomString(6);
57 | } while (checkGidExist(gid));
58 |
59 | // 3. 添加分组
60 | GroupDO groupDO = new GroupDO();
61 | groupDO.setGid(gid);
62 | groupDO.setUsername(UserContext.getUsername());
63 | groupDO.setGroupName(groupName);
64 | int insert = groupMapper.insert(groupDO);
65 | ThrowUtil.throwServerIf(insert != 1, ErrorCode.GROUP_SAVE_ERROR);
66 |
67 | // 4. 将gid添加到布隆过滤器中
68 | gidGenerateCachePenetrationBloomFilter.add(gid);
69 | }
70 |
71 | @Override
72 | public List listGroup() {
73 | // 1. 从上下文中获取用户名
74 | String username = UserContext.getUsername();
75 |
76 | // 2. 构造查询条件
77 | LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
78 | queryWrapper.eq(GroupDO::getUsername, username);
79 | queryWrapper.orderByDesc(GroupDO::getSortOrder);
80 | queryWrapper.orderByDesc(GroupDO::getUpdatedTime);
81 |
82 | // 3. 查询分组列表
83 | List groupDOList = groupMapper.selectList(queryWrapper);
84 | // 3.1 查询分组下的链接数量
85 | List linkCountRespDTOList = linkRemoteService.count(groupDOList.stream().map(GroupDO::getGid).toList()).getData();
86 | // 3.2 拷贝分组列表
87 | List groupRespDTOList = BeanUtil.copyToList(groupDOList, GroupRespDTO.class);
88 | // 3.3 设置分组下的链接数量
89 | groupRespDTOList.forEach(groupRespDTO -> {
90 | for (LinkCountRespDTO linkCountRespDTO : linkCountRespDTOList) {
91 | if (groupRespDTO.getGid().equals(linkCountRespDTO.getGid())) {
92 | groupRespDTO.setLinkCount(linkCountRespDTO.getLinkCount());
93 | break;
94 | }
95 | }
96 | });
97 |
98 | return groupRespDTOList;
99 | }
100 |
101 | @Override
102 | public void update(GroupUpdateReqDTO groupUpdateReqDTO) {
103 | // 1. 校验请求参数
104 | String gid = groupUpdateReqDTO.getGid();
105 | String groupName = groupUpdateReqDTO.getGroupName();
106 | ThrowUtil.throwClientIf(StrUtil.hasBlank(gid, groupName), ErrorCode.CLIENT_ERROR);
107 |
108 | // 2. 构造修改条件
109 | LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
110 | updateWrapper.eq(GroupDO::getGid, gid);
111 | updateWrapper.eq(GroupDO::getUsername, UserContext.getUsername());
112 |
113 | // 3. 修改分组
114 | GroupDO groupDO = new GroupDO();
115 | groupDO.setGroupName(groupName);
116 | int update = groupMapper.update(groupDO, updateWrapper);
117 | ThrowUtil.throwServerIf(update != 1, ErrorCode.GROUP_UPDATE_ERROR);
118 | }
119 |
120 | @Override
121 | public void delete(String gid) {
122 | // 1. 构造删除条件
123 | LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
124 | updateWrapper.eq(GroupDO::getGid, gid);
125 | updateWrapper.eq(GroupDO::getUsername, UserContext.getUsername());
126 |
127 | // 3. 删除分组
128 | int delete = groupMapper.delete(updateWrapper);
129 | ThrowUtil.throwServerIf(delete != 1, ErrorCode.GROUP_DELETE_ERROR);
130 | }
131 |
132 | @Override
133 | public void sort(List groupSortReqDTOList) {
134 | for (GroupSortReqDTO groupSortReqDTO : groupSortReqDTOList) {
135 | // 1. 校验请求参数
136 | String gid = groupSortReqDTO.getGid();
137 | Integer sortOrder = groupSortReqDTO.getSortOrder();
138 | ThrowUtil.throwClientIf(StrUtil.hasBlank(gid) || sortOrder == null, ErrorCode.CLIENT_ERROR);
139 |
140 | // 2. 构造排序修改条件
141 | LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
142 | updateWrapper.eq(GroupDO::getGid, gid);
143 | updateWrapper.eq(GroupDO::getUsername, UserContext.getUsername());
144 |
145 | // 3. 修改排序分组
146 | GroupDO groupDO = new GroupDO();
147 | groupDO.setSortOrder(sortOrder);
148 | int update = groupMapper.update(groupDO, updateWrapper);
149 | ThrowUtil.throwServerIf(update != 1, ErrorCode.GROUP_SORT_ERROR);
150 | }
151 | }
152 |
153 | @Override
154 | public void addDefaultGroup(String username) {
155 | // 1. 生成的分组id不能重复
156 | String gid;
157 | do {
158 | gid = RandomStringUtil.generateRandomString(6);
159 | } while (checkGidExist(gid));
160 |
161 | // 2. 添加默认分组
162 | GroupDO groupDO = new GroupDO();
163 | groupDO.setGid(gid);
164 | groupDO.setUsername(username);
165 | groupDO.setGroupName("默认分组");
166 | int insert = groupMapper.insert(groupDO);
167 | ThrowUtil.throwServerIf(insert != 1, ErrorCode.GROUP_SAVE_ERROR);
168 |
169 | // 3. 将gid添加到布隆过滤器中
170 | gidGenerateCachePenetrationBloomFilter.add(gid);
171 | }
172 |
173 | /**
174 | * 校验Gid是否存在
175 | *
176 | * @param gid 分组ID
177 | * @return 是否存在
178 | */
179 | private boolean checkGidExist(String gid) {
180 | // 判断布隆过滤器是否存在此gid
181 | return gidGenerateCachePenetrationBloomFilter.contains(gid);
182 | }
183 | }
184 |
185 |
186 |
187 |
188 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/service/impl/UserServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.service.impl;
2 |
3 | import cn.hutool.core.bean.BeanUtil;
4 | import cn.hutool.core.lang.UUID;
5 | import cn.hutool.json.JSONUtil;
6 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
7 | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
9 | import com.enndfp.shortlink.admin.common.convention.errorcode.ErrorCode;
10 | import com.enndfp.shortlink.admin.dao.entity.UserDO;
11 | import com.enndfp.shortlink.admin.dao.mapper.UserMapper;
12 | import com.enndfp.shortlink.admin.dto.req.user.UserLoginReqDTO;
13 | import com.enndfp.shortlink.admin.dto.req.user.UserRegisterReqDTO;
14 | import com.enndfp.shortlink.admin.dto.req.user.UserUpdateReqDTO;
15 | import com.enndfp.shortlink.admin.dto.resp.user.UserActualRespDTO;
16 | import com.enndfp.shortlink.admin.dto.resp.user.UserLoginRespDTO;
17 | import com.enndfp.shortlink.admin.dto.resp.user.UserRespDTO;
18 | import com.enndfp.shortlink.admin.service.GroupService;
19 | import com.enndfp.shortlink.admin.service.UserService;
20 | import com.enndfp.shortlink.admin.utils.ThrowUtil;
21 | import jakarta.annotation.Resource;
22 | import org.redisson.api.RBloomFilter;
23 | import org.redisson.api.RLock;
24 | import org.redisson.api.RedissonClient;
25 | import org.springframework.data.redis.core.StringRedisTemplate;
26 | import org.springframework.stereotype.Service;
27 |
28 | import java.util.concurrent.TimeUnit;
29 |
30 | import static com.enndfp.shortlink.admin.common.constant.RedisCacheConstant.*;
31 |
32 | /**
33 | * 用户业务层实现类
34 | *
35 | * @author Enndfp
36 | */
37 | @Service
38 | public class UserServiceImpl extends ServiceImpl implements UserService {
39 |
40 | @Resource
41 | private UserMapper userMapper;
42 | @Resource
43 | private GroupService groupService;
44 | @Resource
45 | private RedissonClient redissonClient;
46 | @Resource
47 | private StringRedisTemplate stringRedisTemplate;
48 | @Resource
49 | private RBloomFilter userRegisterCachePenetrationBloomFilter;
50 |
51 | @Override
52 | public UserRespDTO getUserByUsername(String username) {
53 | // 1. 构造查询条件并查询
54 | LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
55 | queryWrapper.eq(UserDO::getUsername, username);
56 | UserDO userDO = userMapper.selectOne(queryWrapper);
57 |
58 | // 2. 校验用户是否存在
59 | ThrowUtil.throwServerIf(userDO == null, ErrorCode.USER_NOT_EXIST);
60 |
61 | return BeanUtil.toBean(userDO, UserRespDTO.class);
62 | }
63 |
64 | @Override
65 | public UserActualRespDTO getActualUserByUsername(String username) {
66 | // 1. 构造查询条件并查询
67 | LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
68 | queryWrapper.eq(UserDO::getUsername, username);
69 | UserDO userDO = userMapper.selectOne(queryWrapper);
70 |
71 | // 2. 校验用户是否存在
72 | ThrowUtil.throwServerIf(userDO == null, ErrorCode.USER_NOT_EXIST);
73 |
74 | return BeanUtil.toBean(userDO, UserActualRespDTO.class);
75 | }
76 |
77 | @Override
78 | public Boolean checkUsername(String username) {
79 | // 判断布隆过滤器是否存在此用户名
80 | return userRegisterCachePenetrationBloomFilter.contains(username);
81 | }
82 |
83 | @Override
84 | public void register(UserRegisterReqDTO userRegisterReqDTO) {
85 | // 1. 校验用户名是否存在
86 | String username = userRegisterReqDTO.getUsername();
87 | ThrowUtil.throwClientIf(username == null, ErrorCode.USER_NAME_NULL);
88 | ThrowUtil.throwClientIf(checkUsername(username), ErrorCode.USER_NAME_EXIST);
89 |
90 | // 2. 给用户名上锁,防止并发注册
91 | RLock lock = redissonClient.getLock(LOCK_USER_REGISTER_KEY + username);
92 | try {
93 | // 2.1 尝试获取锁
94 | ThrowUtil.throwClientIf(!lock.tryLock(), ErrorCode.USER_NAME_EXIST);
95 |
96 | // 3. 保存用户信息
97 | UserDO userDO = BeanUtil.toBean(userRegisterReqDTO, UserDO.class);
98 | int insert = userMapper.insert(userDO);
99 | ThrowUtil.throwServerIf(insert != 1, ErrorCode.USER_REGISTER_ERROR);
100 |
101 | // 4. 将用户名存入布隆过滤器
102 | userRegisterCachePenetrationBloomFilter.add(username);
103 |
104 | // 5. 创建默认分组
105 | groupService.addDefaultGroup(username);
106 | } finally {
107 | // 5. 释放锁
108 | lock.unlock();
109 | }
110 | }
111 |
112 | @Override
113 | public void update(UserUpdateReqDTO userUpdateReqDTO) {
114 | // TODO 验证当前用户是否为登录用户
115 | // 1. 校验用户名是否存在
116 | String username = userUpdateReqDTO.getUsername();
117 | ThrowUtil.throwClientIf(username == null, ErrorCode.USER_NAME_NULL);
118 |
119 | // 2. 构造更新条件
120 | LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
121 | updateWrapper.eq(UserDO::getUsername, username);
122 | UserDO userDO = BeanUtil.toBean(userUpdateReqDTO, UserDO.class);
123 |
124 | // 3. 执行更新
125 | int update = userMapper.update(userDO, updateWrapper);
126 | ThrowUtil.throwServerIf(update != 1, ErrorCode.USER_INFO_UPDATE_ERROR);
127 | }
128 |
129 | @Override
130 | public UserLoginRespDTO login(UserLoginReqDTO userLoginReqDTO) {
131 | // 1. 校验用户名和密码
132 | String username = userLoginReqDTO.getUsername();
133 | String password = userLoginReqDTO.getPassword();
134 | ThrowUtil.throwClientIf(username == null, ErrorCode.USER_NAME_NULL);
135 | ThrowUtil.throwClientIf(password == null, ErrorCode.PASSWORD_NULL);
136 |
137 | // 2. 校验用户是否存在
138 | LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
139 | queryWrapper.eq(UserDO::getUsername, username);
140 | queryWrapper.eq(UserDO::getPassword, password);
141 | UserDO userDO = userMapper.selectOne(queryWrapper);
142 | ThrowUtil.throwServerIf(userDO == null, ErrorCode.USER_NOT_EXIST);
143 |
144 | // 3. 校验用户是否已登录
145 | Boolean hasLogin = stringRedisTemplate.hasKey(USER_LOGIN_KEY + username);
146 | ThrowUtil.throwServerIf(Boolean.TRUE.equals(hasLogin), ErrorCode.USER_HAS_LOGIN);
147 |
148 | // 4. 生成token并存入redis
149 | String token = UUID.randomUUID().toString(true);
150 | stringRedisTemplate.opsForHash().put(USER_LOGIN_KEY + username, token, JSONUtil.toJsonStr(userDO));
151 | stringRedisTemplate.expire(USER_LOGIN_KEY + username, USER_LOGIN_TTL, TimeUnit.DAYS);
152 |
153 | return new UserLoginRespDTO(token);
154 | }
155 |
156 | @Override
157 | public Boolean checkLogin(String username, String token) {
158 | // 检查redis中是否存在token
159 | return stringRedisTemplate.opsForHash().get(USER_LOGIN_KEY + username, token) != null;
160 | }
161 |
162 | @Override
163 | public void logout(String username, String token) {
164 | // 1. 判断用户是否登录
165 | ThrowUtil.throwServerIf(!checkLogin(username, token), ErrorCode.USER_HAS_LOGIN);
166 |
167 | // 2. 删除redis中的token
168 | stringRedisTemplate.opsForHash().delete(USER_LOGIN_KEY + username, token);
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/utils/RandomStringUtil.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.utils;
2 |
3 | import java.security.SecureRandom;
4 |
5 | /**
6 | * 生成随机字符串工具类
7 | *
8 | * @author Enndfp
9 | */
10 | public class RandomStringUtil {
11 |
12 | /**
13 | * 定义小写字母字符串
14 | */
15 | private static final String CHAR_LOWER = "abcdefghijklmnopqrstuvwxyz";
16 | /**
17 | * 将小写字母字符串转换为大写
18 | */
19 | private static final String CHAR_UPPER = CHAR_LOWER.toUpperCase();
20 | /**
21 | * 定义数字字符串
22 | */
23 | private static final String NUMBER = "0123456789";
24 |
25 | /**
26 | * 将小写字母、大写字母和数字组合成一个用于生成随机字符串的数据源
27 | */
28 | private static final String DATA_FOR_RANDOM_STRING = CHAR_LOWER + CHAR_UPPER + NUMBER;
29 | /**
30 | * 使用SecureRandom来确保生成的随机数更安全
31 | */
32 | private static final SecureRandom RANDOM = new SecureRandom();
33 |
34 | /**
35 | * 生成指定长度的随机字符串
36 | *
37 | * @param length 随机字符串长度
38 | * @return 随机字符串
39 | */
40 | public static String generateRandomString(int length) {
41 | if (length < 1) {
42 | throw new IllegalArgumentException();
43 | }
44 |
45 | StringBuilder sb = new StringBuilder(length);
46 | for (int i = 0; i < length; i++) {
47 | // 生成一个随机索引,用于从数据源字符串中选择字符
48 | int rndCharAt = RANDOM.nextInt(DATA_FOR_RANDOM_STRING.length());
49 | char rndChar = DATA_FOR_RANDOM_STRING.charAt(rndCharAt);
50 |
51 | // 将选择的字符添加到字符串构建器中
52 | sb.append(rndChar);
53 | }
54 |
55 | return sb.toString();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/utils/ResultUtil.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.utils;
2 |
3 | import com.enndfp.shortlink.admin.common.convention.errorcode.ErrorCode;
4 | import com.enndfp.shortlink.admin.common.convention.exception.AbstractException;
5 | import com.enndfp.shortlink.admin.common.convention.result.Result;
6 |
7 | import java.util.Map;
8 | import java.util.Optional;
9 |
10 | /**
11 | * 全局返回对象构造器
12 | *
13 | * @author Enndfp
14 | */
15 | public class ResultUtil {
16 |
17 | /**
18 | * 构造无返回数据成功响应
19 | */
20 | public static Result success() {
21 | return new Result<>("200", "操作成功!", null, null);
22 | }
23 |
24 | /**
25 | * 构造带返回数据的成功响应
26 | */
27 | public static Result success(T data) {
28 | return new Result("200", "操作成功!", data, null);
29 | }
30 |
31 | /**
32 | * 构建服务端失败响应
33 | */
34 | public static Result failure() {
35 | return new Result<>(ErrorCode.SERVICE_ERROR.code(), ErrorCode.SERVICE_ERROR.message(), null, null);
36 | }
37 |
38 | /**
39 | * 通过 {@link AbstractException} 构建失败响应
40 | */
41 | public static Result failure(AbstractException abstractException) {
42 | String errorCode = Optional.ofNullable(abstractException.getErrorCode())
43 | .orElse(ErrorCode.SERVICE_ERROR.code());
44 | String errorMessage = Optional.ofNullable(abstractException.getErrorMessage())
45 | .orElse(ErrorCode.SERVICE_ERROR.message());
46 | return new Result<>(errorCode, errorMessage, null, null);
47 | }
48 |
49 | /**
50 | * 通过 errorCode、errorMessage 构建失败响应
51 | */
52 | public static Result failure(String errorCode, String errorMessage) {
53 | return new Result<>(errorCode, errorMessage, null, null);
54 | }
55 |
56 | /**
57 | * 通过 errorCode、errorMessage、data 构建失败响应
58 | */
59 | public static Result failure(ErrorCode errorCode, Map map) {
60 | return new Result<>(errorCode.code(), errorCode.message(), map, null);
61 | }
62 |
63 | /**
64 | * 通过 errorCode、errorMessage、data 构建失败响应
65 | */
66 | public static Result failure(ErrorCode errorCode, String message) {
67 | return new Result<>(errorCode.code(), message, null, null);
68 | }
69 | }
--------------------------------------------------------------------------------
/shortlink-admin/src/main/java/com/enndfp/shortlink/admin/utils/ThrowUtil.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin.utils;
2 |
3 |
4 | import com.enndfp.shortlink.admin.common.convention.errorcode.ErrorCode;
5 | import com.enndfp.shortlink.admin.common.convention.exception.AbstractException;
6 | import com.enndfp.shortlink.admin.common.convention.exception.ClientException;
7 | import com.enndfp.shortlink.admin.common.convention.exception.RemoteException;
8 | import com.enndfp.shortlink.admin.common.convention.exception.ServerException;
9 |
10 | /**
11 | * 抛异常工具类
12 | *
13 | * @author Enndfp
14 | */
15 | public class ThrowUtil {
16 |
17 | /**
18 | * 无条件抛出ClientException
19 | *
20 | * @param errorCode 业务错误码
21 | */
22 | public static void throwClientException(ErrorCode errorCode) {
23 | throw new ClientException(errorCode);
24 | }
25 |
26 | /**
27 | * 无条件抛出RemoteException
28 | *
29 | * @param errorCode 业务错误码
30 | */
31 | public static void throwRemoteException(ErrorCode errorCode) {
32 | throw new RemoteException(errorCode);
33 | }
34 |
35 | /**
36 | * 无条件抛出ServerException
37 | *
38 | * @param errorCode 业务错误码
39 | */
40 | public static void throwServerException(ErrorCode errorCode) {
41 | throw new ServerException(errorCode);
42 | }
43 |
44 | /**
45 | * 条件成立则抛出指定的异常
46 | *
47 | * @param condition 条件
48 | * @param exception 要抛出的异常
49 | */
50 | public static void throwIf(boolean condition, AbstractException exception) {
51 | if (condition) {
52 | throw exception;
53 | }
54 | }
55 |
56 | /**
57 | * 条件成立则抛出ClientException
58 | *
59 | * @param condition 条件
60 | * @param errorCode 业务错误码
61 | */
62 | public static void throwClientIf(boolean condition, ErrorCode errorCode) {
63 | throwIf(condition, new ClientException(errorCode, errorCode.message()));
64 | }
65 |
66 | /**
67 | * 条件成立则抛出RemoteException
68 | *
69 | * @param condition 条件
70 | * @param errorCode 业务错误码
71 | */
72 | public static void throwRemoteIf(boolean condition, ErrorCode errorCode) {
73 | throwIf(condition, new RemoteException(errorCode, errorCode.message()));
74 | }
75 |
76 | /**
77 | * 条件成立则抛出ServerException
78 | *
79 | * @param condition 条件
80 | * @param errorCode 业务错误码
81 | */
82 | public static void throwServerIf(boolean condition, ErrorCode errorCode) {
83 | throwIf(condition, new ServerException(errorCode, errorCode.message()));
84 | }
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/shortlink-admin/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8002
3 | servlet:
4 | context-path: /api/short-link/admin/v1
5 |
6 | spring:
7 | datasource:
8 | # ShardingSphere 对 Driver 自定义,实现分库分表等隐藏逻辑
9 | driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
10 | # ShardingSphere 配置文件路径
11 | url: jdbc:shardingsphere:classpath:shardingsphere-config-${database.env:dev}.yml
12 | data:
13 | redis:
14 | host: 127.0.0.1
15 | port: 6379
16 | password: 123321
17 |
18 | mybatis-plus:
19 | configuration:
20 | map-underscore-to-camel-case: true
21 | log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
22 | global-config:
23 | db-config:
24 | logic-delete-field: isDeleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
25 | logic-delete-value: 1 # 逻辑已删除值(默认为 1)
26 | logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
27 |
28 | # 日志级别
29 | logging:
30 | level:
31 | root: info
--------------------------------------------------------------------------------
/shortlink-admin/src/main/resources/shardingsphere-config-dev.yml:
--------------------------------------------------------------------------------
1 | # 数据源集合
2 | dataSources:
3 | ds_0:
4 | dataSourceClassName: com.zaxxer.hikari.HikariDataSource
5 | driverClassName: com.mysql.cj.jdbc.Driver
6 | jdbcUrl: jdbc:mysql://127.0.0.1:3306/link?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
7 | username: root
8 | password: 123456
9 |
10 | rules:
11 | - !SHARDING
12 | tables:
13 | t_user:
14 | # 真实数据节点,比如数据库源以及数据库在数据库中真实存在的
15 | actualDataNodes: ds_0.t_user_${0..15}
16 | # 分表策略
17 | tableStrategy:
18 | # 用于单分片键的标准分片场景
19 | standard:
20 | # 分片键
21 | shardingColumn: username
22 | # 分片算法,对应 rules[0].shardingAlgorithms
23 | shardingAlgorithmName: user_table_hash_mod
24 | t_group:
25 | # 真实数据节点,比如数据库源以及数据库在数据库中真实存在的
26 | actualDataNodes: ds_0.t_group_${0..15}
27 | # 分表策略
28 | tableStrategy:
29 | # 用于单分片键的标准分片场景
30 | standard:
31 | # 分片键
32 | shardingColumn: username
33 | # 分片算法,对应 rules[0].shardingAlgorithms
34 | shardingAlgorithmName: group_table_hash_mod
35 | # 分片算法
36 | shardingAlgorithms:
37 | # 数据表分片算法
38 | user_table_hash_mod:
39 | # 根据分片键 Hash 分片
40 | type: HASH_MOD
41 | # 分片数量
42 | props:
43 | sharding-count: 16
44 | # 数据表分片算法
45 | group_table_hash_mod:
46 | # 根据分片键 Hash 分片
47 | type: HASH_MOD
48 | # 分片数量
49 | props:
50 | sharding-count: 16
51 | # 数据加密存储规则
52 | - !ENCRYPT
53 | # 需要加密的表集合
54 | tables:
55 | # 用户表
56 | t_user:
57 | # 用户表中哪些字段需要进行加密
58 | columns:
59 | # 手机号字段,逻辑字段,不一定是在数据库中真实存在
60 | phone:
61 | # 手机号字段存储的密文字段,这个是数据库中真实存在的字段
62 | cipherColumn: phone
63 | # 手机号字段加密算法
64 | encryptorName: common_encryptor
65 | mail:
66 | cipherColumn: mail
67 | encryptorName: common_encryptor
68 | password:
69 | cipherColumn: password
70 | encryptorName: common_encryptor
71 | # 是否按照密文字段查询
72 | queryWithCipherColumn: true
73 | # 加密算法
74 | encryptors:
75 | # 自定义加密算法名称
76 | common_encryptor:
77 | # 加密算法类型
78 | type: AES
79 | props:
80 | # AES 加密密钥
81 | aes-key-value: d6oadClrrb9A3GWo
82 | # 展现逻辑 SQL & 真实 SQL
83 | props:
84 | sql-show: true
--------------------------------------------------------------------------------
/shortlink-admin/src/main/resources/shardingsphere-config-prod.yml:
--------------------------------------------------------------------------------
1 | # 数据源集合
2 | dataSources:
3 | ds_0:
4 | dataSourceClassName: com.zaxxer.hikari.HikariDataSource
5 | driverClassName: com.mysql.cj.jdbc.Driver
6 | jdbcUrl: jdbc:mysql://127.0.0.1:3306/link?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
7 | username: root
8 | password: 123456
9 |
10 | rules:
11 | - !SHARDING
12 | tables:
13 | t_user:
14 | # 真实数据节点,比如数据库源以及数据库在数据库中真实存在的
15 | actualDataNodes: ds_0.t_user_${0..15}
16 | # 分表策略
17 | tableStrategy:
18 | # 用于单分片键的标准分片场景
19 | standard:
20 | # 分片键
21 | shardingColumn: username
22 | # 分片算法,对应 rules[0].shardingAlgorithms
23 | shardingAlgorithmName: user_table_hash_mod
24 | # 分片算法
25 | shardingAlgorithms:
26 | # 数据表分片算法
27 | user_table_hash_mod:
28 | # 根据分片键 Hash 分片
29 | type: HASH_MOD
30 | # 分片数量
31 | props:
32 | sharding-count: 16
33 | # 数据加密存储规则
34 | - !ENCRYPT
35 | # 需要加密的表集合
36 | tables:
37 | # 用户表
38 | t_user:
39 | # 用户表中哪些字段需要进行加密
40 | columns:
41 | # 手机号字段,逻辑字段,不一定是在数据库中真实存在
42 | phone:
43 | # 手机号字段存储的密文字段,这个是数据库中真实存在的字段
44 | cipherColumn: phone
45 | # 手机号字段加密算法
46 | encryptorName: common_encryptor
47 | mail:
48 | cipherColumn: mail
49 | encryptorName: common_encryptor
50 | password:
51 | cipherColumn: password
52 | encryptorName: common_encryptor
53 | # 是否按照密文字段查询
54 | queryWithCipherColumn: true
55 | # 加密算法
56 | encryptors:
57 | # 自定义加密算法名称
58 | common_encryptor:
59 | # 加密算法类型
60 | type: AES
61 | props:
62 | # AES 加密密钥
63 | aes-key-value: d6oadClrrb9A3GWo
64 | # 展现逻辑 SQL & 真实 SQL
65 | props:
66 | sql-show: true
--------------------------------------------------------------------------------
/shortlink-admin/src/test/java/com/enndfp/shortlink/admin/UserTableShardingTest.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.admin;
2 |
3 | /**
4 | * @author Enndfp
5 | */
6 | public class UserTableShardingTest {
7 |
8 | public static final String SQL = "CREATE TABLE `t_group_%d` (\n" +
9 | "\t`id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT 'ID',\n" +
10 | "\t`gid` VARCHAR ( 32 ) NOT NULL COMMENT '分组标识',\n" +
11 | "\t`group_name` VARCHAR ( 64 ) NOT NULL COMMENT '分组名称',\n" +
12 | "\t`username` VARCHAR ( 256 ) NOT NULL COMMENT '创建分组用户名',\n" +
13 | "\t`sort_order` INT ( 3 ) DEFAULT 0 NOT NULL COMMENT '分组排序',\n" +
14 | "\t`created_time` datetime DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',\n" +
15 | "\t`updated_time` datetime DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',\n" +
16 | "\t`is_deleted` TINYINT ( 1 ) DEFAULT 0 NOT NULL COMMENT '删除标识 0:未删除 1:已删除',\n" +
17 | "\tPRIMARY KEY ( `id` ),\n" +
18 | "\tUNIQUE KEY `idx_unique_gid` ( `gid` ) USING BTREE \n" +
19 | ") ENGINE = INNODB DEFAULT CHARSET = utf8mb4;";
20 |
21 | public static void main(String[] args) {
22 | for (int i = 0; i < 16; i++) {
23 | System.out.printf((SQL) + "%n", i);
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/shortlink-gateway/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | shortlink
7 | com.enndfp.shortlink
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | shortlink-gateway
13 |
14 |
--------------------------------------------------------------------------------
/shortlink-gateway/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8000
--------------------------------------------------------------------------------
/shortlink-project/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | shortlink
7 | com.enndfp.shortlink
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | shortlink-project
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-web
18 |
19 |
20 |
21 | com.baomidou
22 | mybatis-plus-boot-starter
23 |
24 |
25 |
26 | com.mysql
27 | mysql-connector-j
28 | runtime
29 |
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-starter-jdbc
34 |
35 |
36 |
37 | cn.hutool
38 | hutool-all
39 |
40 |
41 |
42 | org.springframework.boot
43 | spring-boot-starter-data-redis
44 |
45 |
46 |
47 | org.redisson
48 | redisson-spring-boot-starter
49 |
50 |
51 |
52 | org.apache.shardingsphere
53 | shardingsphere-jdbc-core
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/ShortLinkApplication.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project;
2 |
3 | import org.mybatis.spring.annotation.MapperScan;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 |
7 | /**
8 | * @author Enndfp
9 | */
10 | @SpringBootApplication
11 | @MapperScan("com.enndfp.shortlink.project.dao.mapper")
12 | public class ShortLinkApplication {
13 |
14 | public static void main(String[] args) {
15 | SpringApplication.run(ShortLinkApplication.class,args);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/common/convention/errorcode/ErrorCode.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.common.convention.errorcode;
2 |
3 | /**
4 | * 错误码定义
5 | *
6 | * @author Enndfp
7 | */
8 | public enum ErrorCode implements IErrorCode {
9 |
10 | // ========== 一级宏观错误码 客户端错误 ==========
11 | CLIENT_ERROR("A000001", "客户端请求错误"),
12 |
13 | // ========== 二级宏观错误码 ==========
14 | ORIGIN_URL_NULL("A000300", "原始链接为空"),
15 | GROUP_ID_NULL("A000301", "分组标识为空"),
16 |
17 |
18 |
19 | // ========== 一级宏观错误码 系统执行出错 ==========
20 | SERVICE_ERROR("B000001", "服务端错误"),
21 |
22 | // ========== 二级宏观错误码 ==========
23 | LINK_CREATE_ERROR("B000300", "短链接创建失败"),
24 | LINK_FREQUENT_CREATE("B000301", "短链接频繁生成,请稍后再试"),
25 |
26 |
27 |
28 | // ========== 一级宏观错误码 调用第三方服务出错 ==========
29 | REMOTE_ERROR("C000001", "调用第三方服务出错");
30 |
31 | private final String code;
32 |
33 | private final String message;
34 |
35 | ErrorCode(String code, String message) {
36 | this.code = code;
37 | this.message = message;
38 | }
39 |
40 | @Override
41 | public String code() {
42 | return code;
43 | }
44 |
45 | @Override
46 | public String message() {
47 | return message;
48 | }
49 | }
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/common/convention/errorcode/IErrorCode.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.common.convention.errorcode;
2 |
3 | /**
4 | * 平台错误码
5 | *
6 | * @author Enndfp
7 | */
8 | public interface IErrorCode {
9 |
10 | /**
11 | * 错误码
12 | *
13 | * @return 错误码
14 | */
15 | String code();
16 |
17 | /**
18 | * 错误信息
19 | *
20 | * @return 错误信息
21 | */
22 | String message();
23 | }
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/common/convention/exception/AbstractException.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.common.convention.exception;
2 |
3 | import com.enndfp.shortlink.project.common.convention.errorcode.IErrorCode;
4 | import lombok.Getter;
5 | import org.springframework.util.StringUtils;
6 |
7 | import java.util.Optional;
8 |
9 | /**
10 | * 抽象项目中三类异常体系,客户端异常、服务端异常以及远程服务调用异常
11 | *
12 | * @author Enndfp
13 | * @see ClientException
14 | * @see ServerException
15 | * @see RemoteException
16 | */
17 | @Getter
18 | public abstract class AbstractException extends RuntimeException {
19 |
20 | public final String errorCode;
21 |
22 | public final String errorMessage;
23 |
24 | public AbstractException(String message, Throwable throwable, IErrorCode errorCode) {
25 | super(message, throwable);
26 | this.errorCode = errorCode.code();
27 | this.errorMessage = Optional.ofNullable(StringUtils.hasLength(message) ? message : null).orElse(errorCode.message());
28 | }
29 | }
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/common/convention/exception/ClientException.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.common.convention.exception;
2 |
3 | import com.enndfp.shortlink.project.common.convention.errorcode.ErrorCode;
4 | import com.enndfp.shortlink.project.common.convention.errorcode.IErrorCode;
5 |
6 | /**
7 | * 客户端异常
8 | *
9 | * @author Enndfp
10 | */
11 | public class ClientException extends AbstractException {
12 |
13 | public ClientException(IErrorCode errorCode) {
14 | this(null, null, errorCode);
15 | }
16 |
17 | public ClientException(IErrorCode errorCode,String message) {
18 | this(message, null, errorCode);
19 | }
20 |
21 | public ClientException(String message) {
22 | this(message, null, ErrorCode.CLIENT_ERROR);
23 | }
24 |
25 | public ClientException(String message, IErrorCode errorCode) {
26 | this(message, null, errorCode);
27 | }
28 |
29 | public ClientException(String message, Throwable throwable, IErrorCode errorCode) {
30 | super(message, throwable, errorCode);
31 | }
32 |
33 | @Override
34 | public String toString() {
35 | return "ClientException{" +
36 | "code='" + errorCode + "'," +
37 | "message='" + errorMessage + "'" +
38 | '}';
39 | }
40 | }
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/common/convention/exception/GlobalExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.common.convention.exception;
2 |
3 | import com.enndfp.shortlink.project.common.convention.errorcode.ErrorCode;
4 | import com.enndfp.shortlink.project.common.convention.result.Result;
5 | import com.enndfp.shortlink.project.utils.ResultUtil;
6 | import lombok.SneakyThrows;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.springframework.validation.BindingResult;
9 | import org.springframework.validation.FieldError;
10 | import org.springframework.web.bind.MethodArgumentNotValidException;
11 | import org.springframework.web.bind.annotation.ExceptionHandler;
12 | import org.springframework.web.bind.annotation.RestControllerAdvice;
13 |
14 | import java.util.HashMap;
15 | import java.util.List;
16 | import java.util.Map;
17 |
18 | /**
19 | * 全局异常处理器
20 | *
21 | * @author Enndfp
22 | */
23 | @Slf4j
24 | @RestControllerAdvice
25 | public class GlobalExceptionHandler {
26 |
27 | /**
28 | * 拦截参数验证异常
29 | */
30 | @SneakyThrows
31 | @ExceptionHandler(MethodArgumentNotValidException.class)
32 | public Result> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex) {
33 | BindingResult bindingResult = ex.getBindingResult();
34 | Map errorMap = getErrors(bindingResult);
35 | log.error("methodArgumentNotValidExceptionHandler: " + ex.getMessage() + errorMap, ex);
36 | return ResultUtil.failure();
37 | }
38 |
39 | /**
40 | * 拦截自定义抛出的异常
41 | */
42 | @ExceptionHandler(AbstractException.class)
43 | public Result> abstractException(AbstractException ex) {
44 | log.error("abstractException: " + ex.getMessage(), ex);
45 | return ResultUtil.failure(ex.getErrorCode(), ex.getMessage());
46 | }
47 |
48 | /**
49 | * 运行时异常捕获
50 | */
51 | @ExceptionHandler(RuntimeException.class)
52 | public Result> runtimeExceptionHandler(RuntimeException ex) {
53 | log.error("runtimeException: " + ex.getMessage(), ex);
54 | return ResultUtil.failure();
55 | }
56 |
57 | /**
58 | * 从方法参数校验异常中获取详细异常信息
59 | *
60 | * @param bindingResult
61 | * @return
62 | */
63 | private Map getErrors(BindingResult bindingResult) {
64 |
65 | Map errorMap = new HashMap<>(16);
66 | List errorList = bindingResult.getFieldErrors();
67 |
68 | for (FieldError error : errorList) {
69 | // 错误所对应的属性字段名
70 | String field = error.getField();
71 | // 错误所对应的信息
72 | String message = error.getDefaultMessage();
73 | errorMap.put(field, message);
74 | }
75 | return errorMap;
76 | }
77 | }
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/common/convention/exception/RemoteException.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.common.convention.exception;
2 |
3 | import com.enndfp.shortlink.project.common.convention.errorcode.ErrorCode;
4 | import com.enndfp.shortlink.project.common.convention.errorcode.IErrorCode;
5 |
6 | /**
7 | * 远程服务调用异常
8 | *
9 | * @author Enndfp
10 | */
11 | public class RemoteException extends AbstractException {
12 |
13 | public RemoteException(IErrorCode errorCode) {
14 | this(null, null, errorCode);
15 | }
16 |
17 | public RemoteException(IErrorCode errorCode,String message) {
18 | this(message, null, errorCode);
19 | }
20 | public RemoteException(String message) {
21 | this(message, null, ErrorCode.REMOTE_ERROR);
22 | }
23 |
24 | public RemoteException(String message, IErrorCode errorCode) {
25 | this(message, null, errorCode);
26 | }
27 |
28 | public RemoteException(String message, Throwable throwable, IErrorCode errorCode) {
29 | super(message, throwable, errorCode);
30 | }
31 |
32 | @Override
33 | public String toString() {
34 | return "RemoteException{" +
35 | "code='" + errorCode + "'," +
36 | "message='" + errorMessage + "'" +
37 | '}';
38 | }
39 | }
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/common/convention/exception/ServerException.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.common.convention.exception;
2 |
3 | import com.enndfp.shortlink.project.common.convention.errorcode.ErrorCode;
4 | import com.enndfp.shortlink.project.common.convention.errorcode.IErrorCode;
5 |
6 | import java.util.Optional;
7 |
8 | /**
9 | * 服务端异常
10 | *
11 | * @author Enndfp
12 | */
13 | public class ServerException extends AbstractException {
14 |
15 | public ServerException(String message) {
16 | this(message, null, ErrorCode.SERVICE_ERROR);
17 | }
18 |
19 | public ServerException(IErrorCode errorCode,String message) {
20 | this(message, null, errorCode);
21 | }
22 |
23 | public ServerException(IErrorCode errorCode) {
24 | this(null, errorCode);
25 | }
26 |
27 | public ServerException(String message, IErrorCode errorCode) {
28 | this(message, null, errorCode);
29 | }
30 |
31 | public ServerException(String message, Throwable throwable, IErrorCode errorCode) {
32 | super(Optional.ofNullable(message).orElse(errorCode.message()), throwable, errorCode);
33 | }
34 |
35 | @Override
36 | public String toString() {
37 | return "ServerException{" +
38 | "code='" + errorCode + "'," +
39 | "message='" + errorMessage + "'" +
40 | '}';
41 | }
42 | }
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/common/convention/result/Result.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.common.convention.result;
2 |
3 | import com.enndfp.shortlink.project.common.convention.errorcode.IErrorCode;
4 | import lombok.Data;
5 | import lombok.experimental.Accessors;
6 |
7 | import java.io.Serial;
8 | import java.io.Serializable;
9 |
10 | /**
11 | * 全局返回对象
12 | *
13 | * @author Enndfp
14 | */
15 | @Data
16 | @Accessors(chain = true)
17 | public class Result implements Serializable {
18 |
19 | @Serial
20 | private static final long serialVersionUID = 5679018624309023727L;
21 |
22 | /**
23 | * 返回码
24 | */
25 | private String code;
26 |
27 | /**
28 | * 返回消息
29 | */
30 | private String message;
31 |
32 | /**
33 | * 响应数据
34 | */
35 | private T data;
36 |
37 | /**
38 | * 请求ID
39 | */
40 | private String requestId;
41 |
42 | public Result(String code, String message, T data, String requestId) {
43 | this.code = code;
44 | this.message = message;
45 | this.data = data;
46 | this.requestId = requestId;
47 | }
48 |
49 | public Result(IErrorCode iErrorCode) {
50 | this(iErrorCode.code(), iErrorCode.message(), null, null);
51 | }
52 | }
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/config/MyBatisPlusConfig.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.config;
2 |
3 | import com.baomidou.mybatisplus.annotation.DbType;
4 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
5 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
6 | import org.mybatis.spring.annotation.MapperScan;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 |
10 | /**
11 | * MyBatis Plus 配置
12 | *
13 | * @author Enndfp
14 | */
15 | @Configuration
16 | public class MyBatisPlusConfig {
17 |
18 | /**
19 | * 拦截器配置
20 | */
21 | @Bean
22 | public MybatisPlusInterceptor mybatisPlusInterceptor() {
23 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
24 | // 分页插件
25 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
26 | return interceptor;
27 | }
28 | }
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/config/RbloomFilterConfig.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.config;
2 |
3 | import org.redisson.api.RBloomFilter;
4 | import org.redisson.api.RedissonClient;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 |
8 | /**
9 | * 布隆过滤器配置
10 | *
11 | * @author Enndfp
12 | */
13 | @Configuration
14 | public class RbloomFilterConfig {
15 |
16 | /**
17 | * 防止短链接生成查询数据库的布隆过滤器
18 | */
19 | @Bean
20 | public RBloomFilter shortUriCreateCachePenetrationBloomFilter(RedissonClient redissonClient) {
21 | // 1. 创建布隆过滤器对象
22 | RBloomFilter cachePenetrationBloomFilter = redissonClient.getBloomFilter("shortUriCreateCachePenetrationBloomFilter");
23 | // 2. 初始化布隆过滤器(预计元素数量为100000000L,误差率为0.001)
24 | cachePenetrationBloomFilter.tryInit(100000000L, 0.001);
25 |
26 | return cachePenetrationBloomFilter;
27 | }
28 | }
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/controller/LinkController.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.controller;
2 |
3 | import com.baomidou.mybatisplus.core.metadata.IPage;
4 | import com.enndfp.shortlink.project.common.convention.errorcode.ErrorCode;
5 | import com.enndfp.shortlink.project.common.convention.result.Result;
6 | import com.enndfp.shortlink.project.dto.req.link.LinkCreateReqDTO;
7 | import com.enndfp.shortlink.project.dto.req.link.LinkPageReqDTO;
8 | import com.enndfp.shortlink.project.dto.resp.link.LinkCountRespDTO;
9 | import com.enndfp.shortlink.project.dto.resp.link.LinkCreateRespDTO;
10 | import com.enndfp.shortlink.project.dto.resp.link.LinkPageRespDTO;
11 | import com.enndfp.shortlink.project.service.LinkService;
12 | import com.enndfp.shortlink.project.utils.ResultUtil;
13 | import com.enndfp.shortlink.project.utils.ThrowUtil;
14 | import jakarta.annotation.Resource;
15 | import org.springframework.web.bind.annotation.*;
16 |
17 | import java.util.List;
18 |
19 | /**
20 | * 短链接控制层
21 | *
22 | * @author Enndfp
23 | */
24 | @RestController
25 | @RequestMapping("/link")
26 | public class LinkController {
27 |
28 | @Resource
29 | private LinkService linkService;
30 |
31 | /**
32 | * 创建短链接
33 | *
34 | * @param linkCreateReqDTO 短链接创建请求传输对象
35 | * @return 短链接创建响应传输对象
36 | */
37 | @PostMapping("/create")
38 | public Result create(@RequestBody LinkCreateReqDTO linkCreateReqDTO) {
39 | // 1. 校验请求参数
40 | ThrowUtil.throwClientIf(linkCreateReqDTO == null, ErrorCode.CLIENT_ERROR);
41 | // 2. 执行创建短链接逻辑
42 | LinkCreateRespDTO linkCreateRespDTO = linkService.create(linkCreateReqDTO);
43 |
44 | return ResultUtil.success(linkCreateRespDTO);
45 | }
46 |
47 | /**
48 | * 分页查询短链接
49 | *
50 | * @param linkPageReqDTO 短链接分页请求传输对象
51 | * @return 短链接分页响应传输对象
52 | */
53 | @GetMapping("/page")
54 | public Result> page(@ModelAttribute LinkPageReqDTO linkPageReqDTO) {
55 | // 1. 校验请求参数
56 | ThrowUtil.throwClientIf(linkPageReqDTO == null, ErrorCode.CLIENT_ERROR);
57 | // 2. 执行分页查询短链接逻辑
58 | IPage linkPageRespDTO = linkService.pageLink(linkPageReqDTO);
59 |
60 | return ResultUtil.success(linkPageRespDTO);
61 | }
62 |
63 | /**
64 | * 统计分组下短链接数量
65 | *
66 | * @param gids 分组标识列表
67 | * @return 分组下的链接数量
68 | */
69 | @GetMapping("/count")
70 | public Result> count(@RequestParam List gids) {
71 | // 1. 校验请求参数
72 | ThrowUtil.throwClientIf(gids == null || gids.isEmpty(), ErrorCode.CLIENT_ERROR);
73 | // 2. 统计分组下短链接数量
74 | List linkCountRespDTOList = linkService.countByGids(gids);
75 |
76 | return ResultUtil.success(linkCountRespDTOList);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/dao/entity/LinkDO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.dao.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.*;
4 |
5 | import java.io.Serial;
6 | import java.io.Serializable;
7 | import java.util.Date;
8 |
9 | import lombok.Data;
10 |
11 | /**
12 | * 短链接持久层实体类
13 | *
14 | * @author Enndfp
15 | */
16 | @Data
17 | @TableName(value = "t_link")
18 | public class LinkDO implements Serializable {
19 |
20 | /**
21 | * ID
22 | */
23 | @TableId(type = IdType.ASSIGN_ID)
24 | private Long id;
25 |
26 | /**
27 | * 域名
28 | */
29 | private String domain;
30 |
31 | /**
32 | * 短链接
33 | */
34 | private String shortUri;
35 |
36 | /**
37 | * 完整短链接
38 | */
39 | private String fullShortUrl;
40 |
41 | /**
42 | * 原始链接
43 | */
44 | private String originUrl;
45 |
46 | /**
47 | * 点击量
48 | */
49 | private Integer clickNum;
50 |
51 | /**
52 | * 分组标识
53 | */
54 | private String gid;
55 |
56 | /**
57 | * 网站图标
58 | */
59 | private String favicon;
60 |
61 | /**
62 | * 启用标识 0:未启用 1:已启用
63 | */
64 | private Integer enableStatus;
65 |
66 | /**
67 | * 创建类型 0:控制台 1:接口
68 | */
69 | private Integer createdType;
70 |
71 | /**
72 | * 有效期类型 0:永久有效 1:用户自定义
73 | */
74 | private Integer validDateType;
75 |
76 | /**
77 | * 有效期
78 | */
79 | private Date validDate;
80 |
81 | /**
82 | * 描述
83 | */
84 | private String description;
85 |
86 | /**
87 | * 创建时间
88 | */
89 | private Date createdTime;
90 |
91 | /**
92 | * 修改时间
93 | */
94 | private Date updatedTime;
95 |
96 | /**
97 | * 删除标识 0:未删除 1:已删除
98 | */
99 | @TableLogic
100 | private Integer isDeleted;
101 |
102 | @Serial
103 | @TableField(exist = false)
104 | private static final long serialVersionUID = 1L;
105 | }
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/dao/mapper/LinkMapper.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.dao.mapper;
2 |
3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4 | import com.enndfp.shortlink.project.dao.entity.LinkDO;
5 |
6 | /**
7 | * 短链接持久层接口
8 | *
9 | * @author Enndfp
10 | */
11 | public interface LinkMapper extends BaseMapper {
12 |
13 | }
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/dto/req/link/LinkCreateReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.dto.req.link;
2 |
3 | import com.fasterxml.jackson.annotation.JsonFormat;
4 | import lombok.Data;
5 |
6 | import java.util.Date;
7 |
8 | /**
9 | * 短链接创建请求传输对象
10 | *
11 | * @author Enndfp
12 | */
13 | @Data
14 | public class LinkCreateReqDTO {
15 |
16 | /**
17 | * 域名
18 | */
19 | private String domain;
20 |
21 | /**
22 | * 原始链接
23 | */
24 | private String originUrl;
25 |
26 | /**
27 | * 分组标识
28 | */
29 | private String gid;
30 |
31 | /**
32 | * 创建类型 0:控制台 1:接口
33 | */
34 | private Integer createdType;
35 |
36 | /**
37 | * 有效期类型 0:永久有效 1:用户自定义
38 | */
39 | private Integer validDateType;
40 |
41 | /**
42 | * 有效期
43 | */
44 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
45 | private Date validDate;
46 |
47 | /**
48 | * 描述
49 | */
50 | private String description;
51 | }
52 |
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/dto/req/link/LinkPageReqDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.dto.req.link;
2 |
3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4 | import com.enndfp.shortlink.project.dao.entity.LinkDO;
5 | import lombok.Data;
6 |
7 | /**
8 | * 短链接分页请求传输对象
9 | *
10 | * @author Enndfp
11 | */
12 | @Data
13 | public class LinkPageReqDTO extends Page {
14 |
15 | /**
16 | * 分组标识
17 | */
18 | private String gid;
19 | }
20 |
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/dto/resp/link/LinkCountRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.dto.resp.link;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 链接数量返回数据传输对象
7 | *
8 | * @author Enndfp
9 | */
10 | @Data
11 | public class LinkCountRespDTO {
12 |
13 | /**
14 | * 分组标识
15 | */
16 | private String gid;
17 |
18 | /**
19 | * 分组下的链接数量
20 | */
21 | private String linkCount;
22 | }
23 |
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/dto/resp/link/LinkCreateRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.dto.resp.link;
2 |
3 | import lombok.Data;
4 |
5 | import java.util.Date;
6 |
7 | /**
8 | * 短链接创建响应传输对象
9 | *
10 | * @author Enndfp
11 | */
12 | @Data
13 | public class LinkCreateRespDTO {
14 |
15 | /**
16 | * 分组标识
17 | */
18 | private String gid;
19 |
20 | /**
21 | * 完整短链接
22 | */
23 | private String fullShortUrl;
24 |
25 | /**
26 | * 原始链接
27 | */
28 | private String originUrl;
29 | }
30 |
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/dto/resp/link/LinkPageRespDTO.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.dto.resp.link;
2 |
3 | import com.fasterxml.jackson.annotation.JsonFormat;
4 | import lombok.Data;
5 |
6 | import java.util.Date;
7 |
8 | /**
9 | * 短链接分页响应传输对象
10 | *
11 | * @author Enndfp
12 | */
13 | @Data
14 | public class LinkPageRespDTO {
15 |
16 | /**
17 | * ID
18 | */
19 | private Long id;
20 |
21 | /**
22 | * 域名
23 | */
24 | private String domain;
25 |
26 | /**
27 | * 短链接
28 | */
29 | private String shortUri;
30 |
31 | /**
32 | * 完整短链接
33 | */
34 | private String fullShortUrl;
35 |
36 | /**
37 | * 原始链接
38 | */
39 | private String originUrl;
40 |
41 | /**
42 | * 分组标识
43 | */
44 | private String gid;
45 |
46 | /**
47 | * 网站图标
48 | */
49 | private String favicon;
50 |
51 | /**
52 | * 有效期类型 0:永久有效 1:用户自定义
53 | */
54 | private Integer validDateType;
55 |
56 | /**
57 | * 有效期
58 | */
59 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
60 | private Date validDate;
61 |
62 | /**
63 | * 描述
64 | */
65 | private String description;
66 |
67 | /**
68 | * 创建时间
69 | */
70 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
71 | private Date createdTime;
72 | }
73 |
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/service/LinkService.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.service;
2 |
3 | import com.baomidou.mybatisplus.core.metadata.IPage;
4 | import com.baomidou.mybatisplus.extension.service.IService;
5 | import com.enndfp.shortlink.project.dao.entity.LinkDO;
6 | import com.enndfp.shortlink.project.dto.req.link.LinkCreateReqDTO;
7 | import com.enndfp.shortlink.project.dto.req.link.LinkPageReqDTO;
8 | import com.enndfp.shortlink.project.dto.resp.link.LinkCountRespDTO;
9 | import com.enndfp.shortlink.project.dto.resp.link.LinkCreateRespDTO;
10 | import com.enndfp.shortlink.project.dto.resp.link.LinkPageRespDTO;
11 |
12 | import java.util.List;
13 |
14 | /**
15 | * 短链接业务层接口
16 | *
17 | * @author Enndfp
18 | */
19 | public interface LinkService extends IService {
20 |
21 | /**
22 | * 创建短链接
23 | *
24 | * @param linkCreateReqDTO 短链接创建请求传输对象
25 | * @return 短链接创建响应传输对象
26 | */
27 | LinkCreateRespDTO create(LinkCreateReqDTO linkCreateReqDTO);
28 |
29 | /**
30 | * 分页查询短链接
31 | *
32 | * @param linkPageReqDTO 短链接分页请求传输对象
33 | * @return 短链接分页响应传输对象
34 | */
35 | IPage pageLink(LinkPageReqDTO linkPageReqDTO);
36 |
37 | /**
38 | * 根据分组标识统计链接数量
39 | *
40 | * @param gids 分组标识列表
41 | * @return 链接数量返回数据传输对象列表
42 | */
43 | List countByGids(List gids);
44 | }
45 |
--------------------------------------------------------------------------------
/shortlink-project/src/main/java/com/enndfp/shortlink/project/service/impl/LinkServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.enndfp.shortlink.project.service.impl;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
7 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
8 | import com.baomidou.mybatisplus.core.metadata.OrderItem;
9 |
10 | import cn.hutool.core.bean.BeanUtil;
11 | import cn.hutool.core.util.StrUtil;
12 | import com.baomidou.mybatisplus.core.metadata.IPage;
13 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
14 | import com.enndfp.shortlink.project.common.convention.errorcode.ErrorCode;
15 | import com.enndfp.shortlink.project.common.convention.exception.ServerException;
16 | import com.enndfp.shortlink.project.dao.entity.LinkDO;
17 | import com.enndfp.shortlink.project.dao.mapper.LinkMapper;
18 | import com.enndfp.shortlink.project.dto.req.link.LinkCreateReqDTO;
19 | import com.enndfp.shortlink.project.dto.req.link.LinkPageReqDTO;
20 | import com.enndfp.shortlink.project.dto.resp.link.LinkCountRespDTO;
21 | import com.enndfp.shortlink.project.dto.resp.link.LinkCreateRespDTO;
22 | import com.enndfp.shortlink.project.dto.resp.link.LinkPageRespDTO;
23 | import com.enndfp.shortlink.project.service.LinkService;
24 | import com.enndfp.shortlink.project.utils.HashUtil;
25 | import com.enndfp.shortlink.project.utils.ThrowUtil;
26 | import jakarta.annotation.Resource;
27 | import lombok.extern.slf4j.Slf4j;
28 | import org.redisson.api.RBloomFilter;
29 | import org.springframework.dao.DuplicateKeyException;
30 | import org.springframework.stereotype.Service;
31 |
32 | /**
33 | * 短链接业务层实现类
34 | *
35 | * @author Enndfp
36 | */
37 | @Slf4j
38 | @Service
39 | public class LinkServiceImpl extends ServiceImpl implements LinkService {
40 |
41 | @Resource
42 | private LinkMapper linkMapper;
43 | @Resource
44 | private RBloomFilter shortUriCreateCachePenetrationBloomFilter;
45 |
46 | @Override
47 | public LinkCreateRespDTO create(LinkCreateReqDTO linkCreateReqDTO) {
48 | // 1. 拷贝请求参数到持久层实体类
49 | LinkDO linkDO = BeanUtil.toBean(linkCreateReqDTO, LinkDO.class);
50 |
51 | // 2. 生成短链接后缀
52 | String linkSuffix = generateSuffix(linkCreateReqDTO);
53 |
54 | // 3. 设置持久层实体类属性
55 | String fullShortUrl = linkCreateReqDTO.getDomain() + "/" + linkSuffix;
56 | linkDO.setFullShortUrl(fullShortUrl);
57 | linkDO.setShortUri(linkSuffix);
58 |
59 | // 4. 创建短链接
60 | try {
61 | linkMapper.insert(linkDO);
62 | } catch (DuplicateKeyException ex) {
63 | log.warn("短链接已存在,短链接:{}", fullShortUrl);
64 | throw new ServerException(ErrorCode.LINK_CREATE_ERROR);
65 | } finally {
66 | // 抛不抛异常都要加到布隆过滤器,抛异常说明并发访问布隆过滤器不存在则加入进去,不抛异常说明短链接加入数据库,也要加入进去
67 | shortUriCreateCachePenetrationBloomFilter.add(fullShortUrl);
68 | }
69 |
70 | return BeanUtil.toBean(linkDO, LinkCreateRespDTO.class);
71 | }
72 |
73 | @Override
74 | public IPage pageLink(LinkPageReqDTO linkPageReqDTO) {
75 | // 1. 校验请求参数
76 | String gid = linkPageReqDTO.getGid();
77 | ThrowUtil.throwClientIf(StrUtil.isBlank(gid), ErrorCode.GROUP_ID_NULL);
78 |
79 | // 2. 构造分页查询参数
80 | LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
81 | queryWrapper.eq(LinkDO::getGid, gid);
82 | queryWrapper.eq(LinkDO::getEnableStatus, 1);
83 | queryWrapper.orderByDesc(LinkDO::getCreatedTime);
84 |
85 | // 3. 执行分页查询
86 | IPage linkPage = linkMapper.selectPage(linkPageReqDTO, queryWrapper);
87 | return linkPage.convert(linkDO -> BeanUtil.toBean(linkDO, LinkPageRespDTO.class));
88 | }
89 |
90 | @Override
91 | public List countByGids(List gids) {
92 | // 1. 构造查询条件
93 | QueryWrapper queryWrapper = new QueryWrapper<>();
94 | queryWrapper.select("gid, count(1) as linkCount")
95 | .eq("enable_status", 1)
96 | .in("gid", gids)
97 | .groupBy("gid");
98 |
99 | // 2. 查询分组下的链接数量
100 | List