From e0787230215d50f97b87e6aadf36213e8b4cbb47 Mon Sep 17 00:00:00 2001 From: qyx <565485304@qq.com> Date: Thu, 15 Apr 2021 16:43:57 +0800 Subject: [PATCH] =?UTF-8?q?[=E6=96=B0=E5=A2=9E=E5=8A=9F=E8=83=BD](master):?= =?UTF-8?q?=20=E5=8A=9F=E8=83=BD=E5=BC=80=E5=8F=91=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 开发短链功能 --- .../java/com/by/constants/SystemConstant.java | 7 ++ .../main/java/com/by/utils/ShortUrlUtil.java | 47 ++++++++ .../java/com/by/utils/ThreadLocalUtil.java | 91 ++++++++++++++++ .../main/java/com/by/dto/ShortChainDTO.java | 9 +- .../com/by/dto/ShortChainPointJsonDTO.java | 29 +++++ .../java/com/by/ShortServerApplication.java | 2 + .../by/api/ShortServerOpenApiController.java | 42 ++++++-- .../by/api/constants/RequestInfoConstant.java | 23 ++++ .../api/convert/ShortChainVOToDTOConvert.java | 17 +-- .../src/main/java/com/by/dao/ShortUrlDAO.java | 68 ++++++++++++ .../java/com/by/dao/ShortUrlRepository.java | 14 +++ .../src/main/java/com/by/entity/ShortUrl.java | 37 ++++++- .../entity/constants/DBDefaultConstant.java | 22 ++++ .../java/com/by/exception/BaiyeException.java | 102 ++++++++++++++++++ .../exception/GlobalExceptionHandle.java | 2 +- .../java/com/by/exception/spi/ErrorCode.java | 18 ++++ .../exception/spi/enums/CommonErrorCode.java | 54 ++++++++++ .../com/by/service/ShortServerService.java | 13 ++- .../service/impl/ShortServerServiceImpl.java | 69 ++++++++++-- .../main/java/com/by/task/ShortChainTask.java | 80 +++++++++++++- .../com/by/task/conf/ThreadPoolConfig.java | 9 +- 21 files changed, 718 insertions(+), 37 deletions(-) create mode 100644 short-server-common/src/main/java/com/by/utils/ShortUrlUtil.java create mode 100644 short-server-common/src/main/java/com/by/utils/ThreadLocalUtil.java create mode 100644 short-server-pojo/src/main/java/com/by/dto/ShortChainPointJsonDTO.java create mode 100644 short-server-service/src/main/java/com/by/api/constants/RequestInfoConstant.java create mode 100644 short-server-service/src/main/java/com/by/dao/ShortUrlDAO.java create mode 100644 short-server-service/src/main/java/com/by/entity/constants/DBDefaultConstant.java create mode 100644 short-server-service/src/main/java/com/by/exception/BaiyeException.java rename short-server-service/src/main/java/com/by/{api => }/exception/GlobalExceptionHandle.java (97%) create mode 100644 short-server-service/src/main/java/com/by/exception/spi/ErrorCode.java create mode 100644 short-server-service/src/main/java/com/by/exception/spi/enums/CommonErrorCode.java diff --git a/short-server-common/src/main/java/com/by/constants/SystemConstant.java b/short-server-common/src/main/java/com/by/constants/SystemConstant.java index 845e8d4..6efe987 100644 --- a/short-server-common/src/main/java/com/by/constants/SystemConstant.java +++ b/short-server-common/src/main/java/com/by/constants/SystemConstant.java @@ -2,6 +2,13 @@ package com.by.constants; /** * 系统常量声明 + * @author q */ public class SystemConstant { + + /** + * 默认的IP地址 + */ + public static final String DEFAULT_IP = "0.0.0.0"; + } diff --git a/short-server-common/src/main/java/com/by/utils/ShortUrlUtil.java b/short-server-common/src/main/java/com/by/utils/ShortUrlUtil.java new file mode 100644 index 0000000..df30c2b --- /dev/null +++ b/short-server-common/src/main/java/com/by/utils/ShortUrlUtil.java @@ -0,0 +1,47 @@ +package com.by.utils; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.RandomUtil; + +import java.util.*; + +/** + * 短链生成工具类 + */ +public class ShortUrlUtil { + + /** + * 生成随机串的位数 + */ + private static final Integer BASE_LIMIT_LENGTH = 10; + + private ShortUrlUtil() { + } + + /** + * 生成短链映射[方式1] : 直接用随机字符串进行标识,然后进行 + * + * @return 返回生成的多个短链映射 + */ + public static Map makeShortUrl(List oringinShortUrlList) { + + if (CollectionUtil.isEmpty(oringinShortUrlList)) { + return CollectionUtil.newHashMap(); + } + Map resultMap = new HashMap<>(); + + oringinShortUrlList.forEach( + each -> { + resultMap.put(RandomUtil.randomString(BASE_LIMIT_LENGTH), each); + } + ); + + // 校验生成个数不能少 + if (oringinShortUrlList.size() != resultMap.size()) { + return CollectionUtil.newHashMap(); + } + + return resultMap; + } + +} diff --git a/short-server-common/src/main/java/com/by/utils/ThreadLocalUtil.java b/short-server-common/src/main/java/com/by/utils/ThreadLocalUtil.java new file mode 100644 index 0000000..c10859a --- /dev/null +++ b/short-server-common/src/main/java/com/by/utils/ThreadLocalUtil.java @@ -0,0 +1,91 @@ +package com.by.utils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author q + */ +public class ThreadLocalUtil { + + private static final ThreadLocal> THREAD_LOCAL = ThreadLocal.withInitial( + () -> new HashMap<>(4) + ); + + + public static Map getThreadLocal(){ + return THREAD_LOCAL.get(); + } + + public static T get(String key) { + Map map = (Map)THREAD_LOCAL.get(); + return (T)map.get(key); + } + + public static T get(String key,T defaultValue) { + Map map = (Map)THREAD_LOCAL.get(); + return (T)map.get(key) == null ? defaultValue : (T)map.get(key); + } + + public static void set(String key, Object value) { + Map map = (Map)THREAD_LOCAL.get(); + map.put(key, value); + } + + public static void set(Map keyValueMap) { + Map map = (Map)THREAD_LOCAL.get(); + map.putAll(keyValueMap); + } + + public static void remove() { + THREAD_LOCAL.remove(); + } + + public static Map fetchVarsByPrefix(String prefix) { + Map vars = new HashMap<>(); + if( prefix == null ){ + return vars; + } + Map map = (Map)THREAD_LOCAL.get(); + Set set = map.entrySet(); + + for( Map.Entry entry : set){ + Object key = entry.getKey(); + if( key instanceof String ){ + if( ((String) key).startsWith(prefix) ){ + vars.put((String)key,(T)entry.getValue()); + } + } + } + return vars; + } + + public static T remove(String key) { + Map map = (Map)THREAD_LOCAL.get(); + return (T)map.remove(key); + } + + public static void clear(String prefix) { + if( prefix == null ){ + return; + } + Map map = (Map)THREAD_LOCAL.get(); + Set set = map.entrySet(); + List removeKeys = new ArrayList<>(); + + for( Map.Entry entry : set ){ + Object key = entry.getKey(); + if( key instanceof String ){ + if( ((String) key).startsWith(prefix) ){ + removeKeys.add((String)key); + } + } + } + for( String key : removeKeys ){ + map.remove(key); + } + } +} \ No newline at end of file diff --git a/short-server-pojo/src/main/java/com/by/dto/ShortChainDTO.java b/short-server-pojo/src/main/java/com/by/dto/ShortChainDTO.java index 554f00f..c821753 100644 --- a/short-server-pojo/src/main/java/com/by/dto/ShortChainDTO.java +++ b/short-server-pojo/src/main/java/com/by/dto/ShortChainDTO.java @@ -1,20 +1,23 @@ package com.by.dto; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.util.List; -import java.util.Set; /** * @author q */ @Data +@AllArgsConstructor +@NoArgsConstructor public class ShortChainDTO { /** - * 原始短链集合 + * 原始链接集合 */ - private Set shortChainOrigins; + private List shortChainOrigins; /** * 短链生成结果 diff --git a/short-server-pojo/src/main/java/com/by/dto/ShortChainPointJsonDTO.java b/short-server-pojo/src/main/java/com/by/dto/ShortChainPointJsonDTO.java new file mode 100644 index 0000000..93791d1 --- /dev/null +++ b/short-server-pojo/src/main/java/com/by/dto/ShortChainPointJsonDTO.java @@ -0,0 +1,29 @@ +package com.by.dto; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用于消息发送的dto + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ShortChainPointJsonDTO { + + /** + * 原始链接 + */ + @JSONField(name = "originUrl") + private String originUrl; + + /** + * 点击标识 0-未点击 1-点击 + */ + @JSONField(name = "pointTag") + private Integer pointTag; + + +} diff --git a/short-server-service/src/main/java/com/by/ShortServerApplication.java b/short-server-service/src/main/java/com/by/ShortServerApplication.java index 5009dac..ef9d01d 100644 --- a/short-server-service/src/main/java/com/by/ShortServerApplication.java +++ b/short-server-service/src/main/java/com/by/ShortServerApplication.java @@ -3,12 +3,14 @@ package com.by; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; /** * 项目启动入口 * * @author q */ +@EnableAsync @SpringBootApplication public class ShortServerApplication { public static void main(String[] args) { diff --git a/short-server-service/src/main/java/com/by/api/ShortServerOpenApiController.java b/short-server-service/src/main/java/com/by/api/ShortServerOpenApiController.java index 7212f91..9f864a1 100644 --- a/short-server-service/src/main/java/com/by/api/ShortServerOpenApiController.java +++ b/short-server-service/src/main/java/com/by/api/ShortServerOpenApiController.java @@ -5,16 +5,25 @@ import com.by.api.common.CommonResponse; import com.by.api.convert.ShortChainVOToDTOConvert; import com.by.api.vo.ShortChainRequestVO; import com.by.api.vo.ShortChainResponseVO; +import com.by.constants.SystemConstant; import com.by.dto.ShortChainDTO; +import com.by.entity.ShortUrl; import com.by.service.ShortServerService; +import com.by.task.ShortChainTask; +import com.by.utils.ThreadLocalUtil; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Controller; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; +import javax.servlet.http.HttpServletRequest; import java.util.List; +import java.util.Objects; + +import static com.by.api.constants.RequestInfoConstant.CUSTOMER_IP_ADDR_KEY; /** * @author q @@ -28,22 +37,32 @@ public class ShortServerOpenApiController { private final ShortServerService shortServerService; - public ShortServerOpenApiController(ShortServerService shortServerService) { + private final ShortChainTask shortChainTask; + + public ShortServerOpenApiController(ShortServerService shortServerService, ShortChainTask shortChainTask) { this.shortServerService = shortServerService; + this.shortChainTask = shortChainTask; } @ApiOperation("批量生成短链接") @PostMapping(value = "/trans") @ResponseBody - public CommonResponse parseContentToShortChain(@Validated @RequestBody ShortChainRequestVO shortChainRequestVO) { + public CommonResponse parseContentToShortChain(@Validated @RequestBody ShortChainRequestVO shortChainRequestVO, HttpServletRequest request) { log.info("=== [ShortServerOpenApiController|parseContentToShortChain, one request is coming, request vo is {} ] ===", shortChainRequestVO.toString()); ShortChainDTO shortChainDTO = ShortChainVOToDTOConvert.convertShortChainRequestVOToDTO(shortChainRequestVO); + // 拿到远程请求客户端的Ip信息 + String remoteAddr = request.getRemoteAddr(); + if (StringUtils.isNotBlank(remoteAddr)){ + ThreadLocalUtil.set(CUSTOMER_IP_ADDR_KEY, remoteAddr); + }else { + ThreadLocalUtil.set(CUSTOMER_IP_ADDR_KEY, SystemConstant.DEFAULT_IP); + } + shortChainDTO = shortServerService.handleOriginUrlsToShortUrls(shortChainDTO); List shortChainResult = shortChainDTO.getShortChainResult(); - ShortChainResponseVO shortChainResponseVO = new ShortChainResponseVO(); if (CollectionUtil.isNotEmpty(shortChainResult)){ shortChainResponseVO.setShortChainResult(shortChainResult); @@ -53,11 +72,20 @@ public class ShortServerOpenApiController { } @ApiOperation("短链接兑换长链接并进行") - @GetMapping(value = "/redeem") - public ModelAndView redeemShortChainClick(@RequestParam(value = "a")String param){ - log.info("=== [ShortServerOpenApiController|redeemShortChainClick, one request is coming, request param is {} ] ===", param); + @GetMapping(value = "/s/{redeem}") + public ModelAndView redeemShortChainClick(@PathVariable("redeem") String redeem){ + log.info("=== [ShortServerOpenApiController|redeemShortChainClick, one request is coming, request param is {} ] ===", redeem); + + ShortUrl shortUrl = shortServerService.handleOnceShortUrlToRedirectOriginUrlAndRecord(redeem); + + String originUrl = ""; + if (!Objects.isNull(shortUrl)){ + // 异步进行更新数据库中的点击记录及推送给之前的调用方(可以走消息) + shortChainTask.doRunTask(shortUrl); + originUrl = shortUrl.getOriginUrl(); + } - return shortServerService.handleOnceShortUrlToRedirectOriginUrlAndRecord(param); + return new ModelAndView("redirect:" + originUrl); } } diff --git a/short-server-service/src/main/java/com/by/api/constants/RequestInfoConstant.java b/short-server-service/src/main/java/com/by/api/constants/RequestInfoConstant.java new file mode 100644 index 0000000..9a94200 --- /dev/null +++ b/short-server-service/src/main/java/com/by/api/constants/RequestInfoConstant.java @@ -0,0 +1,23 @@ +package com.by.api.constants; + +/** + * 请求信息常量类 + */ +public class RequestInfoConstant { + + /** + * 客户端请求地址标识名称 + */ + public static final String CUSTOMER_IP_ADDR_KEY = "customer_ip_addr_key"; + + /** + * 短链对应服务器绑定的域名 + */ + public static final String LOCAL_SERVER_HOST = "http://s.baiyee.vip/"; + + + /** + * Http请求调用失败 重试次数 + */ + public static final int RETRY_COUNT = 3; +} diff --git a/short-server-service/src/main/java/com/by/api/convert/ShortChainVOToDTOConvert.java b/short-server-service/src/main/java/com/by/api/convert/ShortChainVOToDTOConvert.java index 1b3aaa6..1d194b0 100644 --- a/short-server-service/src/main/java/com/by/api/convert/ShortChainVOToDTOConvert.java +++ b/short-server-service/src/main/java/com/by/api/convert/ShortChainVOToDTOConvert.java @@ -5,6 +5,7 @@ import com.by.api.vo.ShortChainRequestVO; import com.by.dto.ShortChainDTO; import org.apache.commons.lang3.StringUtils; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; @@ -14,6 +15,10 @@ import java.util.Set; public class ShortChainVOToDTOConvert { + /** + * URL后缀通配 防止Url拼接问题 + */ + private static final String URL_END_TAG = "//"; /** @@ -31,9 +36,9 @@ public class ShortChainVOToDTOConvert { ); - if (CollectionUtil.isNotEmpty(originsUrlSet)){ + if (CollectionUtil.isNotEmpty(originsUrlSet)) { ShortChainDTO shortChainDTO = new ShortChainDTO(); - shortChainDTO.setShortChainOrigins(originsUrlSet); + shortChainDTO.setShortChainOrigins(new ArrayList<>(originsUrlSet)); return shortChainDTO; } @@ -41,10 +46,10 @@ public class ShortChainVOToDTOConvert { } private static String checkUrlAllowed(String baseUrlAddr) { - if (!StringUtils.endsWith(baseUrlAddr.trim(), "//")){ - return baseUrlAddr.trim() + "//"; - } - return baseUrlAddr.trim(); + if (!StringUtils.endsWith(baseUrlAddr.trim(), URL_END_TAG)) { + return baseUrlAddr.trim() + URL_END_TAG; + } + return baseUrlAddr.trim(); } diff --git a/short-server-service/src/main/java/com/by/dao/ShortUrlDAO.java b/short-server-service/src/main/java/com/by/dao/ShortUrlDAO.java new file mode 100644 index 0000000..c3952bb --- /dev/null +++ b/short-server-service/src/main/java/com/by/dao/ShortUrlDAO.java @@ -0,0 +1,68 @@ +package com.by.dao; + +import com.by.entity.ShortUrl; +import com.by.utils.ThreadLocalUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; + +import static com.by.api.constants.RequestInfoConstant.CUSTOMER_IP_ADDR_KEY; + +/** + * 除 JPA 其他的操作数据库的方式 + */ +@Component +@Slf4j +public class ShortUrlDAO { + + private final JdbcTemplate jdbcTemplate; + + public ShortUrlDAO(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + + /** + * 批量插入到数据库 + * + * @param list 准备插入的数据 + * @return 成功插入的条数 + */ + public long batchInsertShortUrls(List list){ + String sql = "INSERT INTO `t_short_url` " + + "(operator_ip,origin_url,short_url,point_tag,expiration_day,valid_tag)" + + "VALUES (?, ?, ?, ?, ?, ?)"; + + int[] ints = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { + + @Override + public void setValues(PreparedStatement preparedStatement, int i) throws SQLException { + preparedStatement.setString(1,list.get(i).getOperatorIp().trim()); + preparedStatement.setString(2,list.get(i).getOriginUrl().trim()); + preparedStatement.setString(3,list.get(i).getShortUrl().trim()); + preparedStatement.setInt(4,list.get(i).getPointTag()); + preparedStatement.setInt(5,list.get(i).getExpirationDay()); + preparedStatement.setInt(6,list.get(i).getValidTag()); + } + + @Override + public int getBatchSize() { + return list.size(); + } + }); + + log.info("=== [ ShortUrlDAO|batchInsertShortUrls 受影响的行数为: {} ] === ", ints.length); + + // 添加完成后移除保存的ip地址信息 + ThreadLocalUtil.remove(CUSTOMER_IP_ADDR_KEY); + + return ints.length; + } + + +} diff --git a/short-server-service/src/main/java/com/by/dao/ShortUrlRepository.java b/short-server-service/src/main/java/com/by/dao/ShortUrlRepository.java index f62e785..f8b1f44 100644 --- a/short-server-service/src/main/java/com/by/dao/ShortUrlRepository.java +++ b/short-server-service/src/main/java/com/by/dao/ShortUrlRepository.java @@ -3,9 +3,23 @@ package com.by.dao; import com.by.entity.ShortUrl; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +/** + * @author q + */ @Repository public interface ShortUrlRepository extends JpaRepository, JpaSpecificationExecutor { + /** + * @param shortUrl 短链 + * @return 返回含有真实链接的记录 + */ + ShortUrl findByshortUrl(String shortUrl); + + @Modifying + @Query("update ShortUrl t set t.sendTag = ?1 where t.id = ?2") + void updateSendStatus(Integer sendStatus, Long recId); } \ No newline at end of file diff --git a/short-server-service/src/main/java/com/by/entity/ShortUrl.java b/short-server-service/src/main/java/com/by/entity/ShortUrl.java index ce3d5aa..cc0f9c5 100644 --- a/short-server-service/src/main/java/com/by/entity/ShortUrl.java +++ b/short-server-service/src/main/java/com/by/entity/ShortUrl.java @@ -2,15 +2,26 @@ package com.by.entity; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.copier.CopyOptions; +import com.by.utils.ThreadLocalUtil; import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; import javax.persistence.*; import java.io.Serializable; import java.sql.Timestamp; +import static com.by.api.constants.RequestInfoConstant.CUSTOMER_IP_ADDR_KEY; +import static com.by.entity.constants.DBDefaultConstant.DEFAULT_CREATE_EXPIRATION_DAY; + +/** + * @author q + */ @Entity @Data @Table(name = "t_short_url") +@NoArgsConstructor public class ShortUrl implements Serializable { /** @@ -25,12 +36,14 @@ public class ShortUrl implements Serializable { * 创建时间 */ @Column(name = "gmt_create", nullable = false) + @CreatedDate private Timestamp gmtCreate; /** * 修改时间 */ @Column(name = "gmt_modified", nullable = false) + @LastModifiedDate private Timestamp gmtModified; /** @@ -64,12 +77,34 @@ public class ShortUrl implements Serializable { private Integer expirationDay; /** - * 有效标识 0-无效 1-有效 + * 有效标识 0-有效 1-无效 */ @Column(name = "valid_tag") private Integer validTag; + /** + * 有效标识 0-成功 1-失败 + */ + @Column(name = "send_tag") + private Integer sendTag; + + /** + * 实体拷贝 + */ public void copy(ShortUrl source) { BeanUtil.copyProperties(source, this, CopyOptions.create().setIgnoreNullValue(true)); } + + /** + * 添加一个新的数据库对象 + */ + public ShortUrl(String originUrl, String shortUrl, + Integer pointTag, Integer validTag) { + this.operatorIp = ThreadLocalUtil.get(CUSTOMER_IP_ADDR_KEY); + this.originUrl = originUrl; + this.shortUrl = shortUrl; + this.expirationDay = DEFAULT_CREATE_EXPIRATION_DAY; + this.pointTag = pointTag; + this.validTag = validTag; + } } diff --git a/short-server-service/src/main/java/com/by/entity/constants/DBDefaultConstant.java b/short-server-service/src/main/java/com/by/entity/constants/DBDefaultConstant.java new file mode 100644 index 0000000..5b6dd18 --- /dev/null +++ b/short-server-service/src/main/java/com/by/entity/constants/DBDefaultConstant.java @@ -0,0 +1,22 @@ +package com.by.entity.constants; + +/** + * 数据库一些默认的属性值,用于存储时候给定的默认值 + */ +public class DBDefaultConstant { + + /** + * 默认记录保存时间为 30天 + */ + public static final int DEFAULT_CREATE_EXPIRATION_DAY = 30; + + /** + * 0 + */ + public static final int ZERO_NUM_TAG = 0; + + /** + * 1 + */ + public static final int ONE_NUM_TAG = 1; +} diff --git a/short-server-service/src/main/java/com/by/exception/BaiyeException.java b/short-server-service/src/main/java/com/by/exception/BaiyeException.java new file mode 100644 index 0000000..a86041c --- /dev/null +++ b/short-server-service/src/main/java/com/by/exception/BaiyeException.java @@ -0,0 +1,102 @@ +package com.by.exception; + + +import com.by.exception.spi.ErrorCode; + +/** + * 业务异常封装 + * @author q + */ +public class BaiyeException extends RuntimeException { + + /** + * 错误异常码 + */ + private ErrorCode errorCode; + + /* + 私有化所有的构造方法 + */ + private BaiyeException(ErrorCode errorCode) { + super(errorCode.toString()); + this.errorCode = errorCode; + } + + private BaiyeException(ErrorCode errorCode, String errorMessage) { + super(errorCode.toString() + " - " + errorMessage); + this.errorCode = errorCode; + } + + private BaiyeException(ErrorCode errorCode, String errorMessage, + Throwable cause) { + super(errorCode.toString() + " - " + getMessage(errorMessage) + + " - " + getMessage(cause), cause); + + this.errorCode = errorCode; + } + + /** + * + * @param errorCode 业务错误实体 + * @return 返回错误封装 + */ + public static BaiyeException createBaiyeException(ErrorCode errorCode) { + return new BaiyeException(errorCode); + } + + /** + * + * @param errorCode 业务错误码 + * @param message 业务错误原因 + * @return 返回错误封装 + */ + public static BaiyeException createBaiyeException(ErrorCode errorCode, String message) { + return new BaiyeException(errorCode, message); + } + + /** + * + * @param errorCode 业务错误码 + * @param message 业务错误原因 + * @param cause 错误原因封装 + * @return 返回错误封装 + */ + public static BaiyeException createBaiyeExceptionWithDetail(ErrorCode errorCode, String message, + Throwable cause) { + if (cause instanceof BaiyeException) { + return (BaiyeException) cause; + } + return new BaiyeException(errorCode, message, cause); + } + + /** + * + * @param errorCode 业务错误码 + * @param cause 错误原因封装 + * @return 返回错误封装 + */ + public static BaiyeException asBaiyeException(ErrorCode errorCode, Throwable cause) { + if (cause instanceof BaiyeException) { + return (BaiyeException) cause; + } + return new BaiyeException(errorCode, getMessage(cause), cause); + } + + /** + * 获取错误原因 + * + * @param obj 异常类 + * @return 返回获取的错误原因 + */ + private static String getMessage(Object obj) { + if (obj == null) { + return ""; + } + + if (obj instanceof Throwable) { + return ((Throwable) obj).getMessage(); + } else { + return obj.toString(); + } + } +} diff --git a/short-server-service/src/main/java/com/by/api/exception/GlobalExceptionHandle.java b/short-server-service/src/main/java/com/by/exception/GlobalExceptionHandle.java similarity index 97% rename from short-server-service/src/main/java/com/by/api/exception/GlobalExceptionHandle.java rename to short-server-service/src/main/java/com/by/exception/GlobalExceptionHandle.java index b132d69..2358d62 100644 --- a/short-server-service/src/main/java/com/by/api/exception/GlobalExceptionHandle.java +++ b/short-server-service/src/main/java/com/by/exception/GlobalExceptionHandle.java @@ -1,4 +1,4 @@ -package com.by.api.exception; +package com.by.exception; import com.by.api.common.CommonResponse; import com.by.api.common.ResponseCode; diff --git a/short-server-service/src/main/java/com/by/exception/spi/ErrorCode.java b/short-server-service/src/main/java/com/by/exception/spi/ErrorCode.java new file mode 100644 index 0000000..e7da1b6 --- /dev/null +++ b/short-server-service/src/main/java/com/by/exception/spi/ErrorCode.java @@ -0,0 +1,18 @@ +package com.by.exception.spi; + +/** + * 业务错误码 - 接口 + */ +public interface ErrorCode { + + /** + * 获取错误码编号 + */ + String getCode(); + + /** + * 获取错误码描述 + */ + String getDescription(); + +} diff --git a/short-server-service/src/main/java/com/by/exception/spi/enums/CommonErrorCode.java b/short-server-service/src/main/java/com/by/exception/spi/enums/CommonErrorCode.java new file mode 100644 index 0000000..6f1aefc --- /dev/null +++ b/short-server-service/src/main/java/com/by/exception/spi/enums/CommonErrorCode.java @@ -0,0 +1,54 @@ +package com.by.exception.spi.enums; + + +import com.by.exception.spi.ErrorCode; + +/** + * 通用业务异常码 + */ +public enum CommonErrorCode implements ErrorCode { + + /* + * 定义相关的错误异常 + */ + CONFIG_ERROR(100, "配置异常"), + RUNTIME_ERROR(101, "业务运行时异常"), + DATA_ERROR(102, "数据异常"),; + + /** + * 错误码 + */ + private final int code; + + /** + * 错误详细信息描述 + */ + private final String describe; + + /** + * 私有构造 + */ + private CommonErrorCode(int code, String describe) { + this.code = code; + this.describe = describe; + } + + @Override + public String getCode() { + return ""; + } + + @Override + public String getDescription() { + return ""; + } + + @Override + public String toString() { + return String.format( + "Code:[%s], Describe:[%s]", + this.code, + this.describe + ); + } +} diff --git a/short-server-service/src/main/java/com/by/service/ShortServerService.java b/short-server-service/src/main/java/com/by/service/ShortServerService.java index 786f0ca..c937a2b 100644 --- a/short-server-service/src/main/java/com/by/service/ShortServerService.java +++ b/short-server-service/src/main/java/com/by/service/ShortServerService.java @@ -1,7 +1,7 @@ package com.by.service; import com.by.dto.ShortChainDTO; -import org.springframework.web.servlet.ModelAndView; +import com.by.entity.ShortUrl; /** * 短链信息处理接口 @@ -21,5 +21,14 @@ public interface ShortServerService { /** * 兑换短链跳转并更新点击记录及回传点击记录 */ - ModelAndView handleOnceShortUrlToRedirectOriginUrlAndRecord(String param); + ShortUrl handleOnceShortUrlToRedirectOriginUrlAndRecord(String redeem); + + + /** + * 更新猎河的发送记录 - 单条更新 + * @param recId + * @param sucess + * @return + */ + boolean updateShortUrlToPlatformSendtatus(Long recId, boolean sucess); } diff --git a/short-server-service/src/main/java/com/by/service/impl/ShortServerServiceImpl.java b/short-server-service/src/main/java/com/by/service/impl/ShortServerServiceImpl.java index 61a5bd0..69664e7 100644 --- a/short-server-service/src/main/java/com/by/service/impl/ShortServerServiceImpl.java +++ b/short-server-service/src/main/java/com/by/service/impl/ShortServerServiceImpl.java @@ -1,10 +1,25 @@ package com.by.service.impl; +import com.by.api.constants.RequestInfoConstant; +import com.by.dao.ShortUrlDAO; +import com.by.dao.ShortUrlRepository; import com.by.dto.ShortChainDTO; +import com.by.entity.ShortUrl; +import com.by.entity.constants.DBDefaultConstant; +import com.by.exception.BaiyeException; +import com.by.exception.spi.enums.CommonErrorCode; import com.by.service.ShortServerService; +import com.by.utils.ShortUrlUtil; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; -import org.springframework.web.servlet.ModelAndView; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; /** * 短链服务实现Service @@ -15,27 +30,65 @@ import org.springframework.web.servlet.ModelAndView; @Slf4j public class ShortServerServiceImpl implements ShortServerService { + private final ShortUrlRepository shortUrlRepository; + + private final ShortUrlDAO shortUrlDAO; + + public ShortServerServiceImpl(ShortUrlRepository shortUrlRepository, ShortUrlDAO shortUrlDAO) { + this.shortUrlRepository = shortUrlRepository; + this.shortUrlDAO = shortUrlDAO; + } @Override + @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) public ShortChainDTO handleOriginUrlsToShortUrls(ShortChainDTO shortChainDTO) { - // TODO: 2021/4/14 0014 // 1. 拼接请求调用短链生成器进行所有的短链请求 - + Map makeShortUrlResult = ShortUrlUtil.makeShortUrl(shortChainDTO.getShortChainOrigins()); // 2. 生成的短链请求批量入库 + List shortUrls = new ArrayList<>(); + makeShortUrlResult.forEach( + (shortUrl, originUrl) -> { + shortUrls.add(new ShortUrl(originUrl, shortUrl, DBDefaultConstant.ZERO_NUM_TAG, DBDefaultConstant.ZERO_NUM_TAG)); + } + ); + long result = shortUrlDAO.batchInsertShortUrls(shortUrls); + if (result < 0) { + log.error(" === [ShortServerServiceImpl|handleOriginUrlsToShortUrls , insert data fail, success insert count is {} ] ===", result); + throw BaiyeException.createBaiyeException(CommonErrorCode.DATA_ERROR); + } // 3. 把短链请求 return 给调用方 - return null; + return new ShortChainDTO(null, new ArrayList<>(makeShortUrlResult.keySet())); } @Override - public ModelAndView handleOnceShortUrlToRedirectOriginUrlAndRecord(String param) { + public ShortUrl handleOnceShortUrlToRedirectOriginUrlAndRecord(String redeem) { // 1. 查询库中的短链请求 - + ShortUrl byshortUrl = shortUrlRepository.findByshortUrl(RequestInfoConstant.LOCAL_SERVER_HOST + redeem); // 2. 拿出真实请求进行跳转 + if (Objects.isNull(byshortUrl)) { + return new ShortUrl(); + } - // 3. 异步进行更新数据库中的点击记录及推送给之前的调用方(可以走消息) + String originUrl = byshortUrl.getOriginUrl(); + if (StringUtils.isBlank(originUrl)) { + return new ShortUrl(); + } + return byshortUrl; + } - return new ModelAndView("redirect:https://www.baidu.com/"); + @Override + @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) + public boolean updateShortUrlToPlatformSendtatus(Long recId, boolean sucess) { + if (recId == null) { + return Boolean.FALSE; + } + if (sucess) { + shortUrlRepository.updateSendStatus(1, recId); + }else { + shortUrlRepository.updateSendStatus(0, recId); + } + return Boolean.TRUE; } } diff --git a/short-server-service/src/main/java/com/by/task/ShortChainTask.java b/short-server-service/src/main/java/com/by/task/ShortChainTask.java index d3391e8..ce5cd3a 100644 --- a/short-server-service/src/main/java/com/by/task/ShortChainTask.java +++ b/short-server-service/src/main/java/com/by/task/ShortChainTask.java @@ -1,6 +1,16 @@ package com.by.task; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSON; +import com.by.api.constants.RequestInfoConstant; +import com.by.dao.ShortUrlRepository; +import com.by.dto.ShortChainPointJsonDTO; +import com.by.entity.ShortUrl; +import com.by.entity.constants.DBDefaultConstant; +import com.by.service.ShortServerService; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Scope; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @@ -8,23 +18,85 @@ import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.time.ZoneOffset; +/** + * @author q + */ @Component @Scope("prototype") @Slf4j public class ShortChainTask { + @Value("${upload.platform.url}") + private String uploudPlatformUrl; + + private final ShortUrlRepository shortUrlRepository; + private final ShortServerService shortServerService; + + public ShortChainTask(ShortUrlRepository shortUrlRepository, ShortServerService shortServerService) { + this.shortUrlRepository = shortUrlRepository; + this.shortServerService = shortServerService; + } + @Async(value = "abTaskExecutor") - public void doRunTask(){ + public void doRunTask(ShortUrl shortUrl) { Long satrtMilliSecond = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli(); log.info("====== [ task start running, task name is {} ] ======", "ABDownTask"); - runTask(); + runTask(shortUrl); Long endMilliSecond = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli(); - log.info("====== [ task start end, task name is {},cost milliSecond is {} ] ======", "ABDownTask", (endMilliSecond-satrtMilliSecond)); + log.info("====== [ task start end, task name is {},cost milliSecond is {} ] ======", "ABDownTask", (endMilliSecond - satrtMilliSecond)); + } + + private void runTask(ShortUrl shortUrl) { + + // 修改状态更新点击状态 + shortUrl.setPointTag(DBDefaultConstant.ONE_NUM_TAG); + ShortUrl save = shortUrlRepository.save(shortUrl); + + // 更新点击信息给我们的平台 + sendPointTagToPlatform(save); } - private void runTask() { + private void sendPointTagToPlatform(ShortUrl shortUrl) { + + // 设置请求失败计数 + int count = 0; + // 转成json字符串 + ShortChainPointJsonDTO shortChainPointJsonDTO = + new ShortChainPointJsonDTO(shortUrl.getOriginUrl(), DBDefaultConstant.ONE_NUM_TAG); + String jsonSendContent = JSON.toJSONString(shortChainPointJsonDTO); + + // 失败重发请求3次 + while (count <= RequestInfoConstant.RETRY_COUNT) { + // 调用HTTP请求发送数据 + HttpResponse httpResponse = sendReq(jsonSendContent, uploudPlatformUrl); + if (httpResponse.isOk() && httpResponse.body().contains("0")) { + log.info("========== [liehe request success, response is {} ] ==========", httpResponse.body()); + shortServerService.updateShortUrlToPlatformSendtatus(shortUrl.getId(), Boolean.TRUE); + break; + } else { + count++; + log.error("========== [liehe request fail, response is {} ] ==========", httpResponse.body()); + } + } + if (count > RequestInfoConstant.RETRY_COUNT) { + shortServerService.updateShortUrlToPlatformSendtatus(shortUrl.getId(), Boolean.FALSE); + } } + /** + * 发送请求封装 + * + * @param json 发送内容为Json + * @param url 请求的url + * @return 返回的内容实体 + */ + private HttpResponse sendReq(String json, String url) { + + return HttpRequest + .post(url) + .body(json) + .execute(); + } } diff --git a/short-server-service/src/main/java/com/by/task/conf/ThreadPoolConfig.java b/short-server-service/src/main/java/com/by/task/conf/ThreadPoolConfig.java index 1fd08d1..74d2765 100644 --- a/short-server-service/src/main/java/com/by/task/conf/ThreadPoolConfig.java +++ b/short-server-service/src/main/java/com/by/task/conf/ThreadPoolConfig.java @@ -1,6 +1,7 @@ package com.by.task.conf; +import cn.hutool.core.thread.NamedThreadFactory; import org.apache.tomcat.util.threads.ThreadPoolExecutor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -8,7 +9,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; @@ -29,12 +29,11 @@ public class ThreadPoolConfig { @Value(value = "${short.chain.task.queueCapacity}") private int queueCapacity = 100; @Value(value = "${short.chain.task.ThreadNamePrefix}") - private String ThreadNamePrefix = "MyThreadPoolExecutor-"; + private String ThreadNamePrefix = "ShortChainTaskExecutor-"; /** - * AB 单的异步线程池的配置类 - * @return + * @return 返回定义的线程池 */ @Bean public Executor shortChainTaskExecutor() { @@ -45,7 +44,7 @@ public class ThreadPoolConfig { 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(queueCapacity), - Executors.defaultThreadFactory(), + new NamedThreadFactory(ThreadNamePrefix, false), new ThreadPoolExecutor.DiscardOldestPolicy() );