diff --git a/accompany-admin/accompany-admin-sdk/src/main/java/com/accompany/admin/dto/link/ShortLinkAdminDto.java b/accompany-admin/accompany-admin-sdk/src/main/java/com/accompany/admin/dto/link/ShortLinkAdminDto.java
new file mode 100644
index 000000000..c7f6e7867
--- /dev/null
+++ b/accompany-admin/accompany-admin-sdk/src/main/java/com/accompany/admin/dto/link/ShortLinkAdminDto.java
@@ -0,0 +1,61 @@
+package com.accompany.admin.dto.link;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 18:34
+ * @description:
+ */
+@Data
+public class ShortLinkAdminDto {
+
+ /**
+ * 短链ID
+ */
+ @ExcelProperty("短链ID")
+ private Long id;
+
+ /**
+ * 短链名称
+ */
+ @ExcelProperty("短链名称")
+ private String name;
+
+ /**
+ * 短链
+ */
+ @ExcelProperty("短链")
+ private String url;
+
+ /**
+ * 二维码图片
+ */
+ @ExcelProperty("二维码图片")
+ private String qrcode;
+
+ /**
+ * 定向跳转
+ */
+ @ExcelProperty("定向跳转")
+ private String typeName;
+
+ /**
+ * 生成时间
+ */
+ @ExcelProperty("生成时间")
+ private String createTimeStr;
+
+ /**
+ * 累计点击数
+ */
+ @ExcelProperty("累计点击数")
+ private Integer clickNum;
+
+ /**
+ * 累计点击uv
+ */
+ @ExcelProperty("累计点击uv")
+ private Integer uvNum;
+}
diff --git a/accompany-admin/accompany-admin-sdk/src/main/java/com/accompany/admin/vo/link/ShortLinkAdminVo.java b/accompany-admin/accompany-admin-sdk/src/main/java/com/accompany/admin/vo/link/ShortLinkAdminVo.java
new file mode 100644
index 000000000..4b39ca5b3
--- /dev/null
+++ b/accompany-admin/accompany-admin-sdk/src/main/java/com/accompany/admin/vo/link/ShortLinkAdminVo.java
@@ -0,0 +1,28 @@
+package com.accompany.admin.vo.link;
+
+import com.accompany.business.model.link.ShortLink;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 15:42
+ * @description:
+ */
+@Data
+@ApiModel
+public class ShortLinkAdminVo extends ShortLink {
+
+ /**
+ * 累计点击数
+ */
+ @ApiModelProperty("累计点击数")
+ private Integer clickNum = 0;
+
+ /**
+ * 累计点击uv
+ */
+ @ApiModelProperty("累计点击uv")
+ private Integer uvNum = 0;
+}
diff --git a/accompany-admin/accompany-admin-service/pom.xml b/accompany-admin/accompany-admin-service/pom.xml
index 43646f936..03ae8d041 100644
--- a/accompany-admin/accompany-admin-service/pom.xml
+++ b/accompany-admin/accompany-admin-service/pom.xml
@@ -67,6 +67,11 @@
tomcat-embed-core
compile
+
+ com.google.zxing
+ javase
+ ${zxing.version}
+
\ No newline at end of file
diff --git a/accompany-admin/accompany-admin-service/src/main/java/com/accompany/admin/service/link/ShortLinkAdminService.java b/accompany-admin/accompany-admin-service/src/main/java/com/accompany/admin/service/link/ShortLinkAdminService.java
new file mode 100644
index 000000000..b39661882
--- /dev/null
+++ b/accompany-admin/accompany-admin-service/src/main/java/com/accompany/admin/service/link/ShortLinkAdminService.java
@@ -0,0 +1,56 @@
+package com.accompany.admin.service.link;
+
+import com.accompany.admin.vo.link.ShortLinkAdminVo;
+import com.accompany.business.model.link.ShortLink;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springframework.web.context.request.ServletWebRequest;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 15:35
+ * @description:
+ */
+public interface ShortLinkAdminService {
+
+ /**
+ * 分页
+ *
+ * @param linkId
+ * @param linkName
+ * @param page
+ * @param pageSize
+ * @return
+ */
+ Page page(Long linkId, String linkName, Integer page, Integer pageSize);
+
+ /**
+ * 保存
+ *
+ * @param param
+ */
+ void save(ShortLink param);
+
+ /**
+ * 失效
+ *
+ * @param linkId
+ */
+ void expire(Long linkId);
+
+ /**
+ * 启用
+ *
+ * @param linkId
+ */
+ void enable(Long linkId);
+
+ /**
+ * 导出
+ *
+ * @param linkId
+ * @param linkName
+ * @param servletWebRequest
+ */
+ void export(Long linkId, String linkName, ServletWebRequest servletWebRequest);
+
+}
diff --git a/accompany-admin/accompany-admin-service/src/main/java/com/accompany/admin/service/link/impl/ShortLinkAdminServiceImpl.java b/accompany-admin/accompany-admin-service/src/main/java/com/accompany/admin/service/link/impl/ShortLinkAdminServiceImpl.java
new file mode 100644
index 000000000..829a77aba
--- /dev/null
+++ b/accompany-admin/accompany-admin-service/src/main/java/com/accompany/admin/service/link/impl/ShortLinkAdminServiceImpl.java
@@ -0,0 +1,186 @@
+package com.accompany.admin.service.link.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.util.StrUtil;
+import com.accompany.admin.dto.link.ShortLinkAdminDto;
+import com.accompany.admin.service.link.ShortLinkAdminService;
+import com.accompany.admin.util.QrCodeUtil;
+import com.accompany.admin.vo.link.ShortLinkAdminVo;
+import com.accompany.business.enums.link.ShortLinkTypeEnum;
+import com.accompany.business.model.link.ShortLink;
+import com.accompany.business.model.link.ShortLinkRecord;
+import com.accompany.business.mybatismapper.link.ShortLinkMapper;
+import com.accompany.business.mybatismapper.link.ShortLinkRecordMapper;
+import com.accompany.business.service.api.QinniuService;
+import com.accompany.common.constant.Constant;
+import com.accompany.common.model.PageReq;
+import com.accompany.core.service.SysConfService;
+import com.alibaba.excel.EasyExcel;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.time.DateFormatUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.context.request.ServletWebRequest;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 15:35
+ * @description:
+ */
+@Slf4j
+@Service
+public class ShortLinkAdminServiceImpl implements ShortLinkAdminService {
+
+ @Autowired
+ private ShortLinkMapper shortLinkMapper;
+
+ @Autowired
+ private ShortLinkRecordMapper shortLinkRecordMapper;
+
+ @Autowired
+ private QinniuService qinniuService;
+
+ @Autowired
+ private SysConfService sysConfService;
+
+ @Override
+ public Page page(Long linkId, String linkName, Integer currentPage, Integer pageSize) {
+ IPage page = shortLinkMapper.selectPage(new Page<>(currentPage, pageSize), Wrappers.lambdaQuery()
+ .eq(linkId != null, ShortLink::getId, linkId)
+ .like(StrUtil.isNotEmpty(linkName), ShortLink::getName, linkName)
+ .orderByDesc(ShortLink::getCreateTime));
+ Page iPage = new Page<>(currentPage, pageSize);
+ List admins = new ArrayList<>();
+ List records = page.getRecords();
+ if (CollectionUtil.isNotEmpty(records)) {
+ for (ShortLink record : records) {
+ ShortLinkAdminVo admin = new ShortLinkAdminVo();
+ BeanUtils.copyProperties(record, admin);
+ admin.setClickNum(shortLinkRecordMapper.selectCount(Wrappers.lambdaQuery()
+ .eq(ShortLinkRecord::getLinkId, record.getId())));
+ //UV
+ admin.setUvNum(shortLinkRecordMapper.getUvCount(record.getId()));
+ admins.add(admin);
+ }
+ }
+ iPage.setTotal(page.getTotal());
+ iPage.setRecords(admins);
+ return iPage;
+ }
+
+ @Override
+ public void save(ShortLink param) {
+ String linkUrl = sysConfService.getSysConfValueById(Constant.SysConfId.SHORT_LINK_URL);
+ Long id = param.getId();
+ Date now = new Date();
+ param.setUpdateTime(now);
+ if (id == null) {
+ Random random = new Random();
+ String code;
+ String qrcode = StrUtil.EMPTY;
+ for (code = Integer.toHexString(random.nextInt(900001) + 100000);
+ shortLinkMapper.selectCount(Wrappers.lambdaQuery()
+ .eq(ShortLink::getCode, code)) > 0; code = Integer.toHexString(random.nextInt(900001) + 100000))
+ ;
+ code = code.toLowerCase();
+ String url = linkUrl + code;
+ InputStream inputStream = null;
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+ QrCodeUtil.encode(url, 200, 200, outputStream);
+ inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+ qrcode = qinniuService.uploadByStream(inputStream);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ }
+ param.setUrl(url);
+ param.setCode(code);
+ param.setQrcode(qrcode);
+ param.setCreateTime(now);
+ shortLinkMapper.insert(param);
+ } else {
+ shortLinkMapper.updateById(param);
+ }
+ }
+
+ @Override
+ public void expire(Long linkId) {
+ ShortLink shortLink = shortLinkMapper.selectById(linkId);
+ if (shortLink == null) {
+ return;
+ }
+ shortLink.setIsEnabled(Constant.Yes1No0.NO);
+ shortLink.setExpireTime(new Date());
+ shortLinkMapper.updateById(shortLink);
+ }
+
+ @Override
+ public void enable(Long linkId) {
+ ShortLink shortLink = shortLinkMapper.selectById(linkId);
+ if (shortLink == null) {
+ return;
+ }
+ shortLink.setIsEnabled(Constant.Yes1No0.YES);
+ shortLink.setExpireTime(null);
+ shortLinkMapper.updateById(shortLink);
+ }
+
+ @Override
+ public void export(Long linkId, String linkName, ServletWebRequest servletWebRequest) {
+ PageReq req = new PageReq();
+ req.setPage(1);
+ req.setPageSize(1000000);
+ List datas = new ArrayList<>();
+ Page page = page(linkId, linkName, 1, 100000);
+ List records = page.getRecords();
+ if (CollectionUtil.isNotEmpty(records)) {
+ for (ShortLinkAdminVo record : records) {
+ Integer type = record.getType();
+ Date createTime = record.getCreateTime();
+ ShortLinkAdminDto admin = new ShortLinkAdminDto();
+ BeanUtils.copyProperties(record, admin);
+ String typeName = "应用商店";
+ if (type != null) {
+ if (type == ShortLinkTypeEnum.CUSTOM.ordinal()) {
+ typeName = "自定义URL:" + record.getCustomValue();
+ }
+ }
+ admin.setTypeName(typeName);
+ if (createTime != null) {
+ admin.setCreateTimeStr(DateFormatUtils.format(createTime, DatePattern.NORM_DATETIME_PATTERN));
+ }
+ datas.add(admin);
+ }
+ }
+ if (servletWebRequest.getResponse() != null) {
+ try {
+ //这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
+ servletWebRequest.getResponse().setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+ servletWebRequest.getResponse().setCharacterEncoding("utf-8");
+ //这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
+ String fileName = URLEncoder.encode("短链记录", "UTF-8").replaceAll("\\+", "%20");
+ servletWebRequest.getResponse().setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+ EasyExcel.write(servletWebRequest.getResponse().getOutputStream(), ShortLinkAdminDto.class).sheet("短链记录").doWrite(datas);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+ }
+
+}
diff --git a/accompany-admin/accompany-admin-service/src/main/java/com/accompany/admin/util/QrCodeUtil.java b/accompany-admin/accompany-admin-service/src/main/java/com/accompany/admin/util/QrCodeUtil.java
new file mode 100644
index 000000000..0e29a33de
--- /dev/null
+++ b/accompany-admin/accompany-admin-service/src/main/java/com/accompany/admin/util/QrCodeUtil.java
@@ -0,0 +1,42 @@
+package com.accompany.admin.util;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Hashtable;
+import java.util.Map;
+
+/**
+ * 二维码生成工具类
+ */
+public class QrCodeUtil {
+
+ /**
+ * 生成二维码
+ * David
+ *
+ * @param url url 网址
+ * @param width 二维码宽度
+ * @param height 二维码高度
+ */
+ public static void encode(String url, int width, int height, OutputStream outputStream) {
+ Map hints = new Hashtable<>();
+ // 指定纠错等级
+ hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
+ // 指定编码格式
+ hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
+ try {
+ BitMatrix bitMatrix = new MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, width, height, hints);
+ MatrixToImageWriter.writeToStream(bitMatrix, "png", outputStream);
+ } catch (Exception ignored) {
+ }
+ }
+
+}
diff --git a/accompany-admin/accompany-admin-web/src/main/java/com/accompany/admin/controller/link/ShortLinkAdminController.java b/accompany-admin/accompany-admin-web/src/main/java/com/accompany/admin/controller/link/ShortLinkAdminController.java
new file mode 100644
index 000000000..98b7f0c3a
--- /dev/null
+++ b/accompany-admin/accompany-admin-web/src/main/java/com/accompany/admin/controller/link/ShortLinkAdminController.java
@@ -0,0 +1,101 @@
+package com.accompany.admin.controller.link;
+
+import com.accompany.admin.service.link.ShortLinkAdminService;
+import com.accompany.admin.vo.link.ShortLinkAdminVo;
+import com.accompany.business.model.link.ShortLink;
+import com.accompany.common.model.PageReq;
+import com.accompany.common.result.BusiResult;
+import com.accompany.common.result.PageResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.ServletWebRequest;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 15:41
+ * @description:
+ */
+@Api(tags = "短链管理")
+@RestController
+@RequestMapping("/admin/short/link")
+public class ShortLinkAdminController {
+
+ @Autowired
+ private ShortLinkAdminService shortLinkAdminService;
+
+ /**
+ * 分页
+ *
+ * @param linkId
+ * @param linkName
+ * @param req
+ * @return
+ */
+ @ApiOperation("分页")
+ @GetMapping("page")
+ public PageResult page(Long linkId, String linkName, PageReq req) {
+ return new PageResult<>(shortLinkAdminService.page(linkId, linkName, req.getPage(), req.getPageSize()));
+ }
+
+ /**
+ * 保存
+ *
+ * @param param
+ * @return
+ */
+ @ApiOperation("保存")
+ @PostMapping("save")
+ public BusiResult save(ShortLink param) {
+ shortLinkAdminService.save(param);
+ return BusiResult.success();
+ }
+
+ /**
+ * 失效
+ *
+ * @param linkId
+ * @return
+ */
+ @ApiOperation("失效")
+ @GetMapping("expire")
+ public BusiResult expire(Long linkId) {
+ shortLinkAdminService.expire(linkId);
+ return BusiResult.success();
+ }
+
+ /**
+ * 启用
+ *
+ * @param linkId
+ * @return
+ */
+ @ApiOperation("启用")
+ @GetMapping("enable")
+ public BusiResult enable(Long linkId) {
+ shortLinkAdminService.enable(linkId);
+ return BusiResult.success();
+ }
+
+ /**
+ * 导出
+ *
+ * @param linkId
+ * @param linkName
+ * @param request
+ * @param response
+ */
+ @ApiOperation("导出")
+ @PostMapping("export")
+ public void export(Long linkId, String linkName, HttpServletRequest request, HttpServletResponse response) {
+ shortLinkAdminService.export(linkId, linkName, new ServletWebRequest(request, response));
+ }
+
+}
diff --git a/accompany-admin/accompany-admin-web/src/main/resources/static/html/link/short_link.html b/accompany-admin/accompany-admin-web/src/main/resources/static/html/link/short_link.html
new file mode 100644
index 000000000..357e6c8cf
--- /dev/null
+++ b/accompany-admin/accompany-admin-web/src/main/resources/static/html/link/short_link.html
@@ -0,0 +1,282 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/accompany-admin/accompany-admin-web/src/main/resources/static/plugins/bootstrap-datetimepicker/js/locales/bootstrap-datetimepicker.zh-CN.js b/accompany-admin/accompany-admin-web/src/main/resources/static/plugins/bootstrap-datetimepicker/js/locales/bootstrap-datetimepicker.zh-CN.js
deleted file mode 100644
index 418fb3071..000000000
--- a/accompany-admin/accompany-admin-web/src/main/resources/static/plugins/bootstrap-datetimepicker/js/locales/bootstrap-datetimepicker.zh-CN.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Simplified Chinese translation for bootstrap-datetimepicker
- * Yuan Cheung
- */
-;(function($){
- $.fn.datetimepicker.dates['zh-CN'] = {
- days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
- daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"],
- daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"],
- months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
- monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
- today: "今天",
- suffix: [],
- meridiem: ["上午", "下午"]
- };
-}(jQuery));
diff --git a/accompany-base/accompany-core/src/main/java/com/accompany/common/constant/Constant.java b/accompany-base/accompany-core/src/main/java/com/accompany/common/constant/Constant.java
index 74924394f..c939f0176 100644
--- a/accompany-base/accompany-core/src/main/java/com/accompany/common/constant/Constant.java
+++ b/accompany-base/accompany-core/src/main/java/com/accompany/common/constant/Constant.java
@@ -1824,6 +1824,10 @@ public class Constant {
*/
public static final String SMS_SDK_TYPE = "sms_sdk_type";
+ /**
+ * 短链
+ */
+ public static final String SHORT_LINK_URL = "short_link_url";
}
public static class ActiveMq {
diff --git a/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/enums/link/ShortLinkTypeEnum.java b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/enums/link/ShortLinkTypeEnum.java
new file mode 100644
index 000000000..4c92a527c
--- /dev/null
+++ b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/enums/link/ShortLinkTypeEnum.java
@@ -0,0 +1,19 @@
+package com.accompany.business.enums.link;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 17:03
+ * @description:
+ */
+public enum ShortLinkTypeEnum {
+
+ /**
+ * 应用商店
+ */
+ STORE,
+
+ /**
+ * 自定义链接
+ */
+ CUSTOM;
+}
diff --git a/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/model/link/ShortLink.java b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/model/link/ShortLink.java
new file mode 100644
index 000000000..b4ab894d2
--- /dev/null
+++ b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/model/link/ShortLink.java
@@ -0,0 +1,91 @@
+package com.accompany.business.model.link;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 15:15
+ * @description:
+ */
+@Data
+@TableName("short_link")
+public class ShortLink {
+
+
+ /**
+ * 主键
+ */
+ @ApiModelProperty("主键")
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 编码
+ */
+ @ApiModelProperty("编码")
+ private String code;
+
+ /**
+ * 名称
+ */
+ @ApiModelProperty("名称")
+ private String name;
+
+ /**
+ * 短链
+ */
+ @ApiModelProperty("短链")
+ private String url;
+
+ /**
+ * 二维码
+ */
+ @ApiModelProperty("二维码")
+ private String qrcode;
+
+ /**
+ * 类型
+ */
+ @ApiModelProperty("类型")
+ private Integer type;
+
+ /**
+ * 自定义URL
+ */
+ @ApiModelProperty("自定义URL")
+ private String customValue;
+
+ /**
+ * 是否启用 0 失效 1 启用
+ */
+ @ApiModelProperty("是否启用 0 失效 1 启用")
+ private Integer isEnabled;
+
+ /**
+ * 失效时间
+ */
+ @ApiModelProperty("失效时间")
+ private Date expireTime;
+
+ /**
+ * 创建时间
+ */
+ @ApiModelProperty("创建时间")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ @ApiModelProperty("更新时间")
+ private Date updateTime;
+
+}
diff --git a/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/model/link/ShortLinkRecord.java b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/model/link/ShortLinkRecord.java
new file mode 100644
index 000000000..487fd6ef2
--- /dev/null
+++ b/accompany-business/accompany-business-sdk/src/main/java/com/accompany/business/model/link/ShortLinkRecord.java
@@ -0,0 +1,77 @@
+package com.accompany.business.model.link;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 15:15
+ * @description:
+ */
+@Data
+@TableName("short_link_record")
+public class ShortLinkRecord {
+
+
+ /**
+ * 主键
+ */
+ @ApiModelProperty("主键")
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 短链ID
+ */
+ @ApiModelProperty("短链ID")
+ private Long linkId;
+
+ /**
+ * 系统
+ */
+ @ApiModelProperty("系统")
+ private String os;
+
+ /**
+ * 系统版本
+ */
+ @ApiModelProperty("系统版本")
+ private String osVersion;
+
+ /**
+ * 设备
+ */
+ @ApiModelProperty("设备")
+ private String model;
+
+ /**
+ * IP
+ */
+ @ApiModelProperty("IP")
+ private String clientIp;
+
+ /**
+ * 客户端时间
+ */
+ @ApiModelProperty("客户端时间")
+ private Date clientTime;
+
+ /**
+ * 创建时间
+ */
+ @ApiModelProperty("创建时间")
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ @ApiModelProperty("更新时间")
+ private Date updateTime;
+
+}
diff --git a/accompany-business/accompany-business-service/pom.xml b/accompany-business/accompany-business-service/pom.xml
index 4dc5461e3..36c871d7c 100644
--- a/accompany-business/accompany-business-service/pom.xml
+++ b/accompany-business/accompany-business-service/pom.xml
@@ -88,6 +88,12 @@
org.springframework.boot
spring-boot-test-autoconfigure
+
+
+ eu.bitwalker
+ UserAgentUtils
+ ${bitwalker.version}
+
\ No newline at end of file
diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/link/ShortLinkMapper.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/link/ShortLinkMapper.java
new file mode 100644
index 000000000..27d3b36d1
--- /dev/null
+++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/link/ShortLinkMapper.java
@@ -0,0 +1,12 @@
+package com.accompany.business.mybatismapper.link;
+
+import com.accompany.business.model.link.ShortLink;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 15:26
+ * @description:
+ */
+public interface ShortLinkMapper extends BaseMapper {
+}
diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/link/ShortLinkRecordMapper.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/link/ShortLinkRecordMapper.java
new file mode 100644
index 000000000..12d169d3b
--- /dev/null
+++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/mybatismapper/link/ShortLinkRecordMapper.java
@@ -0,0 +1,21 @@
+package com.accompany.business.mybatismapper.link;
+
+import com.accompany.business.model.link.ShortLinkRecord;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 15:26
+ * @description:
+ */
+public interface ShortLinkRecordMapper extends BaseMapper {
+
+ /**
+ * 获取uv数
+ * @param linkId
+ * @return
+ */
+ Integer getUvCount(@Param("linkId") Long linkId);
+
+}
diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/link/ShortLinkRecordService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/link/ShortLinkRecordService.java
new file mode 100644
index 000000000..48379e1a1
--- /dev/null
+++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/link/ShortLinkRecordService.java
@@ -0,0 +1,12 @@
+package com.accompany.business.service.link;
+
+import com.accompany.business.model.link.ShortLinkRecord;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 15:27
+ * @description:
+ */
+public interface ShortLinkRecordService extends IService {
+}
diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/link/ShortLinkService.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/link/ShortLinkService.java
new file mode 100644
index 000000000..a8170d5e4
--- /dev/null
+++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/link/ShortLinkService.java
@@ -0,0 +1,20 @@
+package com.accompany.business.service.link;
+
+import com.accompany.business.model.link.ShortLink;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 15:27
+ * @description:
+ */
+public interface ShortLinkService extends IService {
+
+ /**
+ * 点击
+ * @param code
+ * @param clientTime
+ */
+ Integer click(String code, String clientTime);
+
+}
diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/link/impl/ShortLinkRecordServiceImpl.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/link/impl/ShortLinkRecordServiceImpl.java
new file mode 100644
index 000000000..30cce0182
--- /dev/null
+++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/link/impl/ShortLinkRecordServiceImpl.java
@@ -0,0 +1,18 @@
+package com.accompany.business.service.link.impl;
+
+import com.accompany.business.model.link.ShortLinkRecord;
+import com.accompany.business.mybatismapper.link.ShortLinkRecordMapper;
+import com.accompany.business.service.link.ShortLinkRecordService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 15:28
+ * @description:
+ */
+@Slf4j
+@Service
+public class ShortLinkRecordServiceImpl extends ServiceImpl implements ShortLinkRecordService {
+}
diff --git a/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/link/impl/ShortLinkServiceImpl.java b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/link/impl/ShortLinkServiceImpl.java
new file mode 100644
index 000000000..7a8ad2b82
--- /dev/null
+++ b/accompany-business/accompany-business-service/src/main/java/com/accompany/business/service/link/impl/ShortLinkServiceImpl.java
@@ -0,0 +1,100 @@
+package com.accompany.business.service.link.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.util.StrUtil;
+import com.accompany.business.enums.link.ShortLinkTypeEnum;
+import com.accompany.business.model.link.ShortLink;
+import com.accompany.business.model.link.ShortLinkRecord;
+import com.accompany.business.mybatismapper.link.ShortLinkMapper;
+import com.accompany.business.mybatismapper.link.ShortLinkRecordMapper;
+import com.accompany.business.service.link.ShortLinkService;
+import com.accompany.common.utils.DateTimeUtil;
+import com.accompany.common.utils.IPUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import eu.bitwalker.useragentutils.Browser;
+import eu.bitwalker.useragentutils.OperatingSystem;
+import eu.bitwalker.useragentutils.UserAgent;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.stereotype.Service;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 15:28
+ * @description:
+ */
+@Slf4j
+@Service
+public class ShortLinkServiceImpl extends ServiceImpl implements ShortLinkService {
+
+ @Autowired
+ private ShortLinkRecordMapper shortLinkRecordMapper;
+
+ @Override
+ public Integer click(String code, String clientTimeStr) {
+ int shortLinkType = ShortLinkTypeEnum.CUSTOM.ordinal();
+ try {
+ Long linkId = null;
+ List shortLinks = baseMapper.selectList(Wrappers.lambdaQuery()
+ .eq(ShortLink::getCode, code));
+ if (CollectionUtil.isNotEmpty(shortLinks)) {
+ ShortLink shortLink = shortLinks.get(0);
+ linkId = shortLink.getId();
+ shortLinkType = shortLink.getType();
+ }
+ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ if (requestAttributes == null) {
+ return shortLinkType;
+ }
+ ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
+ HttpServletRequest request = servletRequestAttributes.getRequest();
+ String os = StrUtil.EMPTY;
+ String osVersion = StrUtil.EMPTY;
+ String model = StrUtil.EMPTY;
+ //设备
+ String userAgentStr = request.getHeader(HttpHeaders.USER_AGENT);
+ if (StrUtil.isNotEmpty(userAgentStr)) {
+ UserAgent userAgent = UserAgent.parseUserAgentString(userAgentStr);
+ OperatingSystem operatingSystem = userAgent.getOperatingSystem();
+ os = operatingSystem.getName();
+ osVersion = String.valueOf(operatingSystem.getId());
+ model = operatingSystem.getDeviceType().getName();
+ }
+ //客户端时间
+ Date clientTime = new Date();
+ if (StrUtil.isNotEmpty(clientTimeStr)) {
+ try {
+ clientTime = DateTimeUtil.convertStrToDate(clientTimeStr, DatePattern.NORM_DATETIME_PATTERN);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+ //IP
+ String ipAddress = IPUtils.getRealIpAddress(request);
+ //记录
+ ShortLinkRecord record = new ShortLinkRecord();
+ record.setLinkId(linkId);
+ record.setClientIp(ipAddress);
+ record.setOs(os);
+ record.setOsVersion(osVersion);
+ record.setModel(model);
+ record.setClientTime(clientTime);
+ record.setCreateTime(new Date());
+ record.setUpdateTime(new Date());
+ shortLinkRecordMapper.insert(record);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return shortLinkType;
+ }
+}
diff --git a/accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/ShortLinkMapper.xml b/accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/ShortLinkMapper.xml
new file mode 100644
index 000000000..98181d388
--- /dev/null
+++ b/accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/ShortLinkMapper.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/ShortLinkRecordMapper.xml b/accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/ShortLinkRecordMapper.xml
new file mode 100644
index 000000000..c80c8c59e
--- /dev/null
+++ b/accompany-business/accompany-business-service/src/main/resources/accompany/sqlmappers/ShortLinkRecordMapper.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/accompany-business/accompany-business-web/src/main/java/com/accompany/business/controller/link/ShortLinkController.java b/accompany-business/accompany-business-web/src/main/java/com/accompany/business/controller/link/ShortLinkController.java
new file mode 100644
index 000000000..9078fe18e
--- /dev/null
+++ b/accompany-business/accompany-business-web/src/main/java/com/accompany/business/controller/link/ShortLinkController.java
@@ -0,0 +1,35 @@
+package com.accompany.business.controller.link;
+
+import com.accompany.business.service.link.ShortLinkService;
+import com.accompany.common.result.BusiResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author: liaozetao
+ * @date: 2023/9/19 19:00
+ * @description:
+ */
+@Api(tags = "短链")
+@RestController
+@RequestMapping("/short/link")
+public class ShortLinkController {
+
+ @Autowired
+ private ShortLinkService shortLinkService;
+
+ /**
+ * 点击
+ * @return
+ */
+ @ApiOperation("点击")
+ @GetMapping("click")
+ public BusiResult click(String code, String clientTime) {
+ shortLinkService.click(code, clientTime);
+ return BusiResult.success();
+ }
+}
diff --git a/accompany-dependencies/pom.xml b/accompany-dependencies/pom.xml
index 3a1063d10..d986e5206 100644
--- a/accompany-dependencies/pom.xml
+++ b/accompany-dependencies/pom.xml
@@ -97,6 +97,8 @@
1.5.0
0.0.20131108.vaadin1
1.64
+ 3.2.0
+ 1.20