├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── docs └── images │ └── 短信.png ├── mini-link-common ├── pom.xml └── src │ └── main │ ├── frontend-deploy │ ├── Dockerfile │ ├── deployment-frontend.yml │ ├── docker-compose.yml │ ├── nginx.conf │ └── service-frontend.yml │ └── java │ └── com │ └── minilink │ ├── MyBatisPlusGenerator.java │ ├── adapter │ ├── KafkaMsgAdapter.java │ ├── LinkUrlAdapter.java │ └── UserAdapter.java │ ├── annotation │ └── NoLogin.java │ ├── config │ ├── GlobalExceptionConfig.java │ ├── MetaObjectConfig.java │ ├── MyThreadPoolExecutor.java │ ├── MybatisPlusConfig.java │ ├── RedisConfig.java │ ├── SnowFlakeConfig.java │ └── SwaggerConfig.java │ ├── constant │ ├── CommonConstant.java │ ├── KafkaConstant.java │ ├── RedisConstant.java │ └── RegexConstant.java │ ├── enums │ ├── BusinessCodeEnum.java │ └── EmailEnum.java │ ├── exception │ └── BusinessException.java │ ├── interceptor │ ├── LoginInterceptor.java │ └── LoginInterceptorHandler.java │ ├── pojo │ ├── dto │ │ ├── LinkUrlSaveDTO.java │ │ ├── LoginDTO.java │ │ └── RegisterDTO.java │ ├── entity │ │ ├── EmailParamEntity.java │ │ └── VisitShortLinkMsg.java │ ├── po │ │ ├── LinkGroup.java │ │ ├── LinkUrlTob.java │ │ ├── LinkUrlToc.java │ │ └── LinkUser.java │ └── vo │ │ ├── LinkUrlTobVO.java │ │ └── UserVO.java │ └── util │ ├── EncryptUtil.java │ ├── HttpServletUtil.java │ ├── IpUtil.java │ ├── JwtUtil.java │ ├── RandomUtil.java │ ├── SnowFlakeUtil.java │ └── resp │ ├── BaseResponse.java │ └── R.java ├── mini-link-core ├── Dockerfile ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── minilink │ │ ├── MiniLinkCoreApplication.java │ │ ├── controller │ │ ├── LinkGroupController.java │ │ ├── LinkUrlTobController.java │ │ └── LinkUrlTocController.java │ │ ├── mapper │ │ ├── LinkGroupMapper.java │ │ ├── LinkUrlTobMapper.java │ │ └── LinkUrlTocMapper.java │ │ ├── service │ │ ├── LinkGroupService.java │ │ ├── LinkUrlTobService.java │ │ ├── LinkUrlTocService.java │ │ └── impl │ │ │ ├── LinkGroupServiceImpl.java │ │ │ ├── LinkUrlTobServiceImpl.java │ │ │ └── LinkUrlTocServiceImpl.java │ │ ├── sharding │ │ ├── ShardingConfigFactory.java │ │ ├── ShardingElement.java │ │ └── algorithm │ │ │ ├── LinkTocDatabaseShardingAlgorithm.java │ │ │ └── LinkTocTableShardingAlgorithm.java │ │ ├── store │ │ ├── LinkGroupStore.java │ │ ├── LinkUrlTobStore.java │ │ ├── LinkUrlTocStore.java │ │ └── impl │ │ │ ├── LinkGroupStoreImpl.java │ │ │ ├── LinkUrlTobStoreImpl.java │ │ │ └── LinkUrlTocStoreImpl.java │ │ └── util │ │ └── LinkUrlUtil.java │ └── resources │ ├── application-dev.yml │ ├── application.yml │ ├── deploy │ ├── deployment-core.yml │ ├── service-core.yml │ └── sql │ │ └── mini_link_core.sql │ ├── mapper │ ├── LinkGroupMapper.xml │ ├── LinkUrlMapper.xml │ └── LinkUrlTocMapper.xml │ └── sharding-dev.yaml ├── mini-link-data ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── minilink │ │ ├── MiniLinkDataApplication.java │ │ ├── controller │ │ └── ShortLinkStatisticsController.java │ │ ├── mapper │ │ └── ShortLinkStatisticsMapper.java │ │ └── service │ │ ├── ShortLinkStatisticsService.java │ │ └── impl │ │ └── ShortLinkStatisticsServiceImpl.java │ └── resources │ ├── application-dev.yml │ ├── application.yml │ └── deploy │ ├── clickhouse.sql │ ├── deployment-data.yml │ └── service-data.yml ├── mini-link-flink ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── minilink │ ├── app │ ├── dwd │ │ └── DwdClickLinkApp.java │ ├── dwm │ │ ├── DwmLinkWideLogApp.java │ │ └── DwmUniqueVisitorApp.java │ ├── dws │ │ └── DwsClickLinkApp.java │ ├── func │ │ ├── DeviceMapFunction.java │ │ ├── LocationMapFunction.java │ │ ├── VisitorStateRichMapFunction.java │ │ └── VisitorUniqueRichFilterFunction.java │ └── sink │ │ └── ClickHouseSink.java │ ├── constant │ └── KafkaConstant.java │ ├── enums │ └── VisitorStateEnum.java │ ├── pojo │ └── VisitShortLinkLog.java │ └── util │ ├── AMapUtil.java │ ├── DateTimeUtil.java │ ├── FlinkKafkaUtil.java │ ├── OkHttpUtil.java │ └── UserAgentUtil.java ├── mini-link-gateway ├── Dockerfile ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── minilink │ │ └── MiniLinkGatewayApplication.java │ └── resources │ ├── application-dev.yml │ ├── application.yml │ └── deploy │ ├── deployment-gateway.yml │ ├── ingress-nginx.yml │ └── service-gateway.yml ├── mini-link-user ├── Dockerfile ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── minilink │ │ ├── MiniLinkUserApplication.java │ │ ├── config │ │ ├── CaptchaConfig.java │ │ └── MinioConfig.java │ │ ├── controller │ │ ├── UserAssistController.java │ │ ├── UserFormController.java │ │ └── UserInfoController.java │ │ ├── mapper │ │ └── LinkUserMapper.java │ │ ├── service │ │ ├── UserAssistService.java │ │ ├── UserFormService.java │ │ ├── UserInfoService.java │ │ └── impl │ │ │ ├── UserAssistServiceImpl.java │ │ │ ├── UserFormServiceImpl.java │ │ │ └── UserInfoServiceImpl.java │ │ ├── store │ │ ├── LinkUserStore.java │ │ └── impl │ │ │ └── LinkUserStoreImpl.java │ │ ├── strategy │ │ └── email │ │ │ ├── AbstractEmailStrategy.java │ │ │ ├── EmailStrategyFactory.java │ │ │ └── handler │ │ │ └── RegisterEmailHandler.java │ │ └── util │ │ └── EmailUtil.java │ └── resources │ ├── application-dev.yml │ ├── application.yml │ ├── deploy │ ├── deployment-user.yml │ ├── mini_link_user.sql │ └── service-user.yml │ └── mapper │ └── LinkUserMapper.xml └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as 7 | contributors and maintainers pledge to making participation in our project, and 8 | our community a harassment-free experience for everyone, regardless of age, body 9 | size, disability, ethnicity, gender identity and expression, level of experience, 10 | education, socio-economic status, nationality, personal appearance, race, 11 | religion, or sexual identity and orientation. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or 42 | reject comments, commits, code, wiki edits, issues, and other contributions 43 | that are not aligned to this Code of Conduct, or to ban temporarily or 44 | permanently any contributor for other behaviors that they deem inappropriate, 45 | threatening, offensive, or harmful. 46 | 47 | ## Scope 48 | 49 | This Code of Conduct applies both within project spaces and in public spaces 50 | when an individual is representing the project or its community. Examples of 51 | representing a project or community include using an official project e-mail 52 | address, posting via an official social media account, or acting as an appointed 53 | representative at an online or offline event. Representation of a project may be 54 | further defined and clarified by project maintainers. 55 | 56 | ## Enforcement 57 | 58 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 59 | reported by contacting the project team at https://knowstreaming.com/support-center . All 60 | complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 73 | 74 | [homepage]: https://www.contributor-covenant.org 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |

麻烦您帮忙点个Star⭐

6 |
7 | 8 | # ✨系统介绍 9 | 10 | Mini Link 是一套用于将长链接压缩为短链接系统,访问用户可以通过短链接快速分享、访问源地址。支持多种使用场景,包括社交媒体分享、营销活动跟踪、数据分析等。 11 |
12 | 系统基于 SpringBoot 3.2 + SpringCloud Alibaba 2023 等主流技术实现的高并发、高性能、海量数据短链接平台。 13 |
14 | 通过 Kafka + Flink + ClickHouse 实现访问行为数据实时数仓,完成海量数据清洗、计算、聚合,提供多维度数据统计面板功能。 15 |
16 | 17 | 商业使用场景: 18 | 1. **在线广告** 19 | 2. **社交媒体** 20 | 3. **电商平台** 21 | 4. **......** 22 | 23 | ![img.png](docs/images/短信.png) 24 |
25 | 26 | 短链接优点: 27 | 28 | 1. **缩短长链接字符数量,简洁美观** 29 | 2. **降低营销短信字数,降低费用成本** 30 | 3. **简化图形二维码生成复杂度** 31 | 4. **隐藏链接业务参数,提高数据安全性** 32 | 5. **方便统计运营数据,挖掘数据价值** 33 | 6. **......** 34 | 35 | # 🚀项目架构 36 | 37 | ## 模块划分 38 | 39 | ``` 40 | mini-link 41 | ├── mini-link-common --- 公共通用 42 | ├── mini-link-core --- 短链接模块 43 | ├── mini-link-data --- 大数据统计看板 44 | ├── mini-link-flink --- 大数据实时计算 45 | ├── mini-link-gateway --- 服务网关 46 | └── mini-link-user --- 用户模块 47 | ``` 48 | 49 | ## 服务端 50 | 51 | | 技术 | 说明 | 官网 | 52 | |----------------------|-----------|------------------------------------------------------------------------------| 53 | | JDK17 | Java开发工具 | https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html | 54 | | Spring Boot | Spring脚手架 | https://spring.io/projects/spring-boot | 55 | | Spring Cloud Alibaba | 微服务框架 | https://github.com/alibaba/spring-cloud-alibaba | 56 | | MySQL | 关系型数据库 | https://github.com/mysqljs/mysql | 57 | | Redis | KV数据库 | https://github.com/redis/redis | 58 | | MyBatis-Plus | ORM框架 | https://baomidou.com/ | 59 | | XXL-JOB | 定时调度 | https://github.com/xuxueli/xxl-job | 60 | | MinIO | 文件存储 | https://github.com/minio/minio | 61 | | ShardingSphere | 分库分表 | https://github.com/apache/shardingsphere | 62 | | Lombok | JavaBean库 | https://github.com/projectlombok/lombok | 63 | | Kafka | 消息队列 | https://github.com/apache/kafka | 64 | | Flink | 流处理框架 | https://github.com/apache/flink | 65 | | ClickHouse | OLAP数据仓库 | https://github.com/ClickHouse/ClickHouse | 66 | 67 | ## 前端 68 | 69 | | 技术 | 说明 | 官网 | 70 | |-------------|----------|---------------------------------| 71 | | Vue | 前端框架 | https://vuejs.org/ | 72 | | Vue-router | 路由框架 | https://router.vuejs.org/ | 73 | | Vuex | 全局状态管理框架 | https://vuex.vuejs.org/ | 74 | | ElementPlus | 前端UI框架 | https://element-plus.org/ | 75 | | TypeScript | 微软开源语言 | https://www.typescriptlang.org/ | 76 | | Axios | HTTP请求库 | https://github.com/axios/axios | 77 | | Echarts | 数据图表 | https://v-charts.js.org/ | 78 | 79 | ## 运维 80 | 81 | | 技术 | 说明 | 官网 | 82 | |---------------|--------|---------------------------------------------| 83 | | Docker | 镜像容器 | https://www.docker.com/ | 84 | | Kubernetes | 容器编排工具 | https://kubernetes.io/ | 85 | | Ingress-Nginx | 负载均衡器 | https://github.com/kubernetes/ingress-nginx | 86 | | Gitlab | 代码仓库 | https://github.com/gitlabhq/gitlabhq | 87 | | Harbor | 镜像仓库 | https://github.com/axios/axios | 88 | | Jenkins | 自动化发布 | https://github.com/jenkinsci/jenkins | 89 | | Prometheus | 数据采集 | https://github.com/prometheus/prometheus | 90 | | Grafana | 数据可视化 | https://github.com/grafana/grafana | 91 | | Logstash | 日志采集 | https://github.com/elastic/logstash | 92 | | Kibana | 日志可视化 | https://github.com/elastic/kibana | 93 | 94 | # 🎉贡献名单 95 | 96 |
97 | 98 | 99 | 100 | | 姓名 | Github | 公司 | 101 | |:---:|:-----------------------------:|:--------------------:| 102 | | 徐志斌 | https://github.com/Binx98 | PARAVERSE TECHNOLOGY | 103 | | Joy | https://github.com/Joydevelop | 保密 | 104 | | 陈辰 | https://github.com/ | SBI BITS | 105 | | 刘贤壮 | https://github.com/ | 江苏中车数字 | 106 | -------------------------------------------------------------------------------- /docs/images/短信.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Binx98/mini-link/77beb09be961aae244a5befed9676f45f7b721c5/docs/images/短信.png -------------------------------------------------------------------------------- /mini-link-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | com.minilink 6 | mini-link 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | 4.0.0 11 | com.minilink 12 | mini-link-common 13 | 0.0.1-SNAPSHOT 14 | mini-link-common 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-web 20 | 21 | 22 | com.alibaba.cloud 23 | spring-cloud-starter-alibaba-nacos-discovery 24 | 25 | 26 | mysql 27 | mysql-connector-java 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-redis 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-validation 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-aop 40 | 41 | 42 | io.jsonwebtoken 43 | jjwt 44 | 45 | 46 | com.baomidou 47 | mybatis-plus-spring-boot3-starter 48 | 49 | 50 | com.baomidou 51 | mybatis-plus-generator 52 | 53 | 54 | com.baomidou 55 | mybatis-plus-jsqlparser 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-freemarker 60 | 61 | 62 | org.projectlombok 63 | lombok 64 | 65 | 66 | org.springdoc 67 | springdoc-openapi-starter-webmvc-ui 68 | 69 | 70 | javax.xml.bind 71 | jaxb-api 72 | 73 | 74 | org.springframework.kafka 75 | spring-kafka 76 | 77 | 78 | cn.hutool 79 | hutool-all 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.springframework.boot 87 | spring-boot-maven-plugin 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /mini-link-common/src/main/frontend-deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.24.0 2 | MAINTAINER 徐志斌 3 | COPY ./dist /usr/share/nginx/html 4 | COPY nginx.conf /etc/nginx/nginx.conf 5 | -------------------------------------------------------------------------------- /mini-link-common/src/main/frontend-deploy/deployment-frontend.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: frontend-deployment 5 | namespace: mini-link-namespace 6 | labels: 7 | app: frontend-label 8 | spec: 9 | replicas: 3 10 | selector: 11 | matchLabels: 12 | app: frontend-label 13 | template: 14 | metadata: 15 | labels: 16 | app: frontend-label 17 | spec: 18 | containers: 19 | - name: frontend 20 | image: 镜像仓库内网IP:端口/minilink/mini-link-frontend:1.0 21 | imagePullPolicy: Always 22 | ports: 23 | - containerPort: 80 24 | resources: 25 | requests: 26 | memory: 300Mi 27 | cpu: 200m 28 | limits: 29 | memory: 500Mi 30 | cpu: 400m 31 | -------------------------------------------------------------------------------- /mini-link-common/src/main/frontend-deploy/docker-compose.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Binx98/mini-link/77beb09be961aae244a5befed9676f45f7b721c5/mini-link-common/src/main/frontend-deploy/docker-compose.yml -------------------------------------------------------------------------------- /mini-link-common/src/main/frontend-deploy/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | include mime.types; 9 | default_type application/octet-stream; 10 | sendfile on; 11 | keepalive_timeout 65; 12 | 13 | server { 14 | listen 80; 15 | server_name localhost; 16 | 17 | location / { 18 | root /usr/share/nginx/html; 19 | try_files $uri $uri/ /index.html; 20 | index index.html index.htm; 21 | } 22 | 23 | location /api/ { 24 | proxy_http_version 1.1; 25 | proxy_set_header Host $host; 26 | proxy_set_header X-Real-IP $remote_addr; 27 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 28 | proxy_set_header Connection ""; 29 | # 这里用的是 K8S Service 服务名访问方式 30 | proxy_pass http://backend-service.prod-env.svc.cluster.local:8080; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mini-link-common/src/main/frontend-deploy/service-frontend.yml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: frontend-service 5 | namespace: mini-link-namespace 6 | labels: 7 | app: frontend-label 8 | spec: 9 | selector: 10 | app: frontend-label 11 | type: ClusterIP 12 | ports: 13 | - protocol: TCP 14 | port: 80 15 | targetPort: 80 16 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/MyBatisPlusGenerator.java: -------------------------------------------------------------------------------- 1 | package com.minilink; 2 | 3 | import com.baomidou.mybatisplus.annotation.FieldFill; 4 | import com.baomidou.mybatisplus.annotation.IdType; 5 | import com.baomidou.mybatisplus.generator.FastAutoGenerator; 6 | import com.baomidou.mybatisplus.generator.config.OutputFile; 7 | import com.baomidou.mybatisplus.generator.config.rules.DateType; 8 | import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; 9 | import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; 10 | import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; 11 | import com.baomidou.mybatisplus.generator.fill.Column; 12 | 13 | import java.sql.Types; 14 | import java.util.Collections; 15 | 16 | /** 17 | * @Author 徐志斌 18 | * @Date: 2024/12/6 14:52 19 | * @Version 1.0 20 | * @Description: MyBatis-Plus 代码生成器 21 | */ 22 | public class MyBatisPlusGenerator { 23 | public static void main(String[] args) { 24 | String url = "jdbc:mysql://localhost:3306/mini_link_core_0?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"; 25 | String username = "root"; 26 | String password = "123456"; 27 | String projectPath = System.getProperty("user.dir"); 28 | FastAutoGenerator.create(url, username, password) 29 | .globalConfig(builder -> { 30 | builder.author("徐志斌") 31 | .dateType(DateType.TIME_PACK) 32 | .commentDate("yyyy-MM-dd") 33 | .outputDir(projectPath + "/src/main/java") 34 | .disableOpenDir(); 35 | }) 36 | .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> { 37 | int typeCode = metaInfo.getJdbcType().TYPE_CODE; 38 | if (typeCode == Types.SMALLINT || typeCode == Types.TINYINT) { 39 | return DbColumnType.INTEGER; 40 | } 41 | return typeRegistry.getColumnType(metaInfo); 42 | })) 43 | .packageConfig(builder -> { 44 | builder.parent("com.minilink") 45 | .controller("controller") 46 | .entity("pojo.po") 47 | .service("service") 48 | .serviceImpl("service.impl") 49 | .mapper("mapper") 50 | .xml("mapper.xml") 51 | .pathInfo(Collections.singletonMap(OutputFile.xml, projectPath + "/src/main/resources/mapper")) 52 | .build(); 53 | }) 54 | .strategyConfig(builder -> { 55 | builder.enableCapitalMode() 56 | .enableSkipView() 57 | .disableSqlFilter() 58 | .addInclude("link_url_toc_0") 59 | .entityBuilder().enableFileOverride().enableLombok() 60 | .enableChainModel() 61 | .enableRemoveIsPrefix() 62 | .enableTableFieldAnnotation() 63 | .logicDeleteColumnName("deleted") 64 | .logicDeletePropertyName("deleted") 65 | .naming(NamingStrategy.underline_to_camel) 66 | .columnNaming(NamingStrategy.underline_to_camel) 67 | .idType(IdType.ASSIGN_ID) 68 | .formatFileName("%s") 69 | .addTableFills(new Column("create_time", FieldFill.INSERT)) 70 | .addTableFills(new Column("update_time", FieldFill.INSERT_UPDATE)) 71 | .addTableFills(new Column("deleted", FieldFill.INSERT)) 72 | .controllerBuilder() 73 | .enableFileOverride() 74 | .enableHyphenStyle() 75 | .enableRestStyle() 76 | .formatFileName("%sController") 77 | .serviceBuilder() 78 | .enableFileOverride() 79 | .formatServiceFileName("%sService") 80 | .formatServiceImplFileName("%sServiceImpl") 81 | .mapperBuilder() 82 | .enableFileOverride() 83 | .enableBaseColumnList() 84 | .enableBaseResultMap() 85 | .formatMapperFileName("%sMapper") 86 | .formatXmlFileName("%sMapper"); 87 | }).templateEngine(new FreemarkerTemplateEngine()) 88 | .templateConfig(builder -> { 89 | builder.controller("/templates/controller.java").service("/templates/service.java") 90 | .serviceImpl("/templates/serviceImpl.java") 91 | .build(); 92 | }).execute(); 93 | System.out.println("=========================代码生成器执行成功!========================="); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/adapter/KafkaMsgAdapter.java: -------------------------------------------------------------------------------- 1 | package com.minilink.adapter; 2 | 3 | import com.minilink.pojo.entity.VisitShortLinkMsg; 4 | import com.minilink.util.IpUtil; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * @Author: 徐志斌 10 | * @CreateTime: 2024-12-20 11:20 11 | * @Description: Kafka 队列消息适配器 12 | * @Version: 1.0 13 | */ 14 | public class KafkaMsgAdapter { 15 | public static VisitShortLinkMsg buildVisitShortLinkMsg(Long accountId, String userAgentStr, String shortLinkCode) { 16 | VisitShortLinkMsg msg = new VisitShortLinkMsg(); 17 | msg.setAccountId(accountId); 18 | msg.setIp(IpUtil.getIpAddr()); 19 | msg.setUserAgent(userAgentStr); 20 | msg.setShortLinkCode(shortLinkCode); 21 | msg.setVisitTime(LocalDateTime.now()); 22 | return msg; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/adapter/LinkUrlAdapter.java: -------------------------------------------------------------------------------- 1 | package com.minilink.adapter; 2 | 3 | import com.minilink.pojo.po.LinkUrlTob; 4 | import com.minilink.pojo.po.LinkUrlToc; 5 | import com.minilink.pojo.vo.LinkUrlTobVO; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * @Author: 徐志斌 13 | * @CreateTime: 2024-12-13 13:21 14 | * @Description: 链接-适配器 15 | * @Version: 1.0 16 | */ 17 | public class LinkUrlAdapter { 18 | public static LinkUrlTob buildLinkUrlTobPO(Long accountId, Long groupId, String title, String icon, 19 | String domain, String shortLinkCode, String shortLink, 20 | String longLink, LocalDateTime expiredTime) { 21 | LinkUrlTob linkUrl = new LinkUrlTob(); 22 | linkUrl.setAccountId(accountId); 23 | linkUrl.setGroupId(groupId); 24 | linkUrl.setTitle(title); 25 | linkUrl.setIcon(icon); 26 | linkUrl.setDomain(domain); 27 | linkUrl.setShortLinkCode(shortLinkCode); 28 | linkUrl.setShortLink(shortLink); 29 | linkUrl.setLongLink(longLink); 30 | linkUrl.setExpiredTime(expiredTime); 31 | return linkUrl; 32 | } 33 | 34 | public static LinkUrlToc buildLinkUrlTocPO(Long id, Long accountId, String shortLinkCode, 35 | String shortLink, String longLink, LocalDateTime expiredTime) { 36 | LinkUrlToc linkUrl = new LinkUrlToc(); 37 | linkUrl.setId(id); 38 | linkUrl.setAccountId(accountId); 39 | linkUrl.setShortLinkCode(shortLinkCode); 40 | linkUrl.setShortLink(shortLink); 41 | linkUrl.setLongLink(longLink); 42 | linkUrl.setExpiredTime(expiredTime); 43 | return linkUrl; 44 | } 45 | 46 | public static LinkUrlTobVO buildLinkUrlTobVO(LinkUrlTob linkUrlTob) { 47 | LinkUrlTobVO linkUrl = new LinkUrlTobVO(); 48 | linkUrl.setTitle(linkUrlTob.getTitle()); 49 | linkUrl.setGroupId(linkUrlTob.getGroupId()); 50 | linkUrl.setAccountId(linkUrlTob.getAccountId()); 51 | linkUrl.setShortLink(linkUrlTob.getShortLink()); 52 | linkUrl.setQrCode(linkUrlTob.getQrCode()); 53 | linkUrl.setLongLink(linkUrlTob.getLongLink()); 54 | linkUrl.setExpiredTime(linkUrlTob.getExpiredTime()); 55 | return linkUrl; 56 | } 57 | 58 | public static List buildLinkUrlTobVOList(List urlTobList) { 59 | List result = new ArrayList<>(); 60 | for (LinkUrlTob urlTob : urlTobList) { 61 | LinkUrlTobVO linkUrl = new LinkUrlTobVO(); 62 | linkUrl.setTitle(urlTob.getTitle()); 63 | linkUrl.setGroupId(urlTob.getGroupId()); 64 | linkUrl.setAccountId(urlTob.getAccountId()); 65 | linkUrl.setShortLink(urlTob.getShortLink()); 66 | linkUrl.setQrCode(urlTob.getQrCode()); 67 | linkUrl.setLongLink(urlTob.getLongLink()); 68 | linkUrl.setExpiredTime(urlTob.getExpiredTime()); 69 | result.add(linkUrl); 70 | } 71 | return result; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/adapter/UserAdapter.java: -------------------------------------------------------------------------------- 1 | package com.minilink.adapter; 2 | 3 | import com.minilink.pojo.po.LinkUser; 4 | import com.minilink.pojo.vo.UserVO; 5 | 6 | /** 7 | * @Author 徐志斌 8 | * @Date: 2024/12/8 15:08 9 | * @Version 1.0 10 | * @Description: 账户相关-适配器 11 | */ 12 | public class UserAdapter { 13 | public static LinkUser buildUserPO(Long accountId, String nickName, String avatar, 14 | String email, String password, String salt) { 15 | LinkUser userPO = new LinkUser(); 16 | userPO.setAccountId(accountId); 17 | userPO.setNickName(nickName); 18 | userPO.setAvatar(avatar); 19 | userPO.setEmail(email); 20 | userPO.setPassword(password); 21 | userPO.setSalt(salt); 22 | return userPO; 23 | } 24 | 25 | public static LinkUser buildUserPO(Long accountId, String nickName, String avatar, String email) { 26 | LinkUser userPO = new LinkUser(); 27 | userPO.setAccountId(accountId); 28 | userPO.setNickName(nickName); 29 | userPO.setAvatar(avatar); 30 | userPO.setEmail(email); 31 | return userPO; 32 | } 33 | 34 | public static UserVO buildUserVO(LinkUser userPO) { 35 | UserVO userVO = new UserVO(); 36 | userVO.setEmail(userPO.getEmail()); 37 | userVO.setNickName(userPO.getNickName()); 38 | userVO.setAvatar(userPO.getAvatar()); 39 | userVO.setCreateTime(userPO.getCreateTime()); 40 | return userVO; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/annotation/NoLogin.java: -------------------------------------------------------------------------------- 1 | package com.minilink.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @Author: 徐志斌 7 | * @CreateTime: 2024-12-20 11:48 8 | * @Description: 免登录注解 9 | * @Version: 1.0 10 | */ 11 | @Target({ElementType.METHOD, ElementType.TYPE}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Documented 14 | public @interface NoLogin { 15 | } 16 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/config/GlobalExceptionConfig.java: -------------------------------------------------------------------------------- 1 | package com.minilink.config; 2 | 3 | import com.minilink.enums.BusinessCodeEnum; 4 | import com.minilink.exception.BusinessException; 5 | import com.minilink.util.resp.R; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.validation.BindException; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | import org.springframework.web.method.annotation.HandlerMethodValidationException; 11 | 12 | /** 13 | * @Author 徐志斌 14 | * @Date: 2024/12/6 11:08 15 | * @Version 1.0 16 | * @Description: 全局异常处理 17 | */ 18 | @Slf4j 19 | @RestControllerAdvice 20 | public class GlobalExceptionConfig { 21 | /** 22 | * 自定义异常 BusinessException 23 | */ 24 | @ExceptionHandler(BusinessException.class) 25 | public R bizException(BusinessException e) { 26 | log.error("-------------bizException:{}-------------", e.getCodeEnum()); 27 | return R.out(e.getCodeEnum()); 28 | } 29 | 30 | /** 31 | * 服务端接口参数校验异常 32 | */ 33 | @ExceptionHandler(BindException.class) 34 | public R validationException(BindException e) { 35 | StringBuilder sb = new StringBuilder(); 36 | e.getBindingResult().getAllErrors().forEach(error -> sb.append(error.getDefaultMessage()).append("\r\n")); 37 | log.error("-------------bindException:{}-------------", e.getMessage()); 38 | return R.out(BusinessCodeEnum.PARAM_ERROR, sb); 39 | } 40 | 41 | @ExceptionHandler(HandlerMethodValidationException.class) 42 | public R handlerMethodValidationException(HandlerMethodValidationException e) { 43 | log.error("-------------handlerMethodValidationException:{}-------------", e.getMessage()); 44 | return R.out(BusinessCodeEnum.REGEX_SHORT_LINK_FORMAT_ERROR, e.getMessage()); 45 | } 46 | 47 | 48 | /** 49 | * 异常兜底 Exception 50 | */ 51 | @ExceptionHandler(Exception.class) 52 | public R exception(Exception e) { 53 | log.error("-------------exception:{}-------------", e.getMessage()); 54 | return R.out(BusinessCodeEnum.FAIL, e.getMessage()); 55 | } 56 | } -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/config/MetaObjectConfig.java: -------------------------------------------------------------------------------- 1 | package com.minilink.config; 2 | 3 | import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.apache.ibatis.reflection.MetaObject; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | /** 11 | * @Author: 徐志斌 12 | * @CreateTime: 2023-10-24 11:48 13 | * @Description: MyBatis-Plus自动填充 14 | * @Version: 1.0 15 | */ 16 | @Slf4j 17 | @Component 18 | public class MetaObjectConfig implements MetaObjectHandler { 19 | @Override 20 | public void insertFill(MetaObject metaObject) { 21 | this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); 22 | this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); 23 | this.strictInsertFill(metaObject, "deleted", Boolean.class, false); 24 | } 25 | 26 | @Override 27 | public void updateFill(MetaObject metaObject) { 28 | this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); 29 | } 30 | } -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/config/MyThreadPoolExecutor.java: -------------------------------------------------------------------------------- 1 | package com.minilink.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 6 | 7 | import java.util.concurrent.ThreadPoolExecutor; 8 | 9 | /** 10 | * @Author: 徐志斌 11 | * @CreateTime: 2024-12-6 20:06 12 | * @Description: 自定义线程池 13 | * @Version: 1.0 14 | */ 15 | @Configuration 16 | public class MyThreadPoolExecutor { 17 | public static final String THREAD_POOL_NAME = "threadPoolTaskExecutor"; 18 | 19 | /** 20 | * CPU 密集型:线程数建议设置为 CPU 核心数 + 1。 21 | * IO 密集型:线程数建议设置为 2 * CPU 核心数。 22 | */ 23 | @Bean(THREAD_POOL_NAME) 24 | public ThreadPoolTaskExecutor threadPoolTaskExecutor() { 25 | int cpuCores = Runtime.getRuntime().availableProcessors(); 26 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 27 | executor.setCorePoolSize(cpuCores + 1); 28 | executor.setMaxPoolSize(cpuCores + 1); 29 | executor.setQueueCapacity(100); 30 | executor.setKeepAliveSeconds(30); 31 | executor.setThreadNamePrefix("mini-link-"); 32 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); 33 | executor.initialize(); 34 | return executor; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/config/MybatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | package com.minilink.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 | * @Author: 徐志斌 11 | * @CreateTime: 2024-12-20 16:14 12 | * @Description: MyBatis-Plus 分页插件 13 | * @Version: 1.0 14 | */ 15 | @Configuration 16 | public class MybatisPlusConfig { 17 | @Bean 18 | public MybatisPlusInterceptor mybatisPlusInterceptor() { 19 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 20 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 21 | return interceptor; 22 | } 23 | } -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.minilink.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.redis.connection.RedisConnectionFactory; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 8 | import org.springframework.data.redis.serializer.StringRedisSerializer; 9 | 10 | /** 11 | * @Author 徐志斌 12 | * @Date: 2024/12/7 10:01 13 | * @Version 1.0 14 | * @Description: Redis 配置类 15 | */ 16 | @Configuration 17 | public class RedisConfig { 18 | @Bean 19 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 20 | RedisTemplate template = new RedisTemplate<>(); 21 | template.setConnectionFactory(factory); 22 | Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class); 23 | template.setKeySerializer(new StringRedisSerializer()); 24 | template.setValueSerializer(serializer); 25 | template.setHashKeySerializer(new StringRedisSerializer()); 26 | template.setHashValueSerializer(serializer); 27 | return template; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/config/SnowFlakeConfig.java: -------------------------------------------------------------------------------- 1 | package com.minilink.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | 5 | import java.net.Inet4Address; 6 | import java.net.InetAddress; 7 | import java.net.UnknownHostException; 8 | 9 | /** 10 | * @Author 徐志斌 11 | * @Date: 2024/12/15 15:14 12 | * @Version 1.0 13 | * @Description: 雪花算法SnowFlake配置类 14 | */ 15 | @Configuration 16 | public class SnowFlakeConfig { 17 | /** 18 | * 通过 Ipv4 地址哈希取模生成 worker-id 19 | */ 20 | static { 21 | InetAddress inetAddress = null; 22 | try { 23 | inetAddress = Inet4Address.getLocalHost(); 24 | String hostAddressIp = inetAddress.getHostAddress(); 25 | String workerId = Math.abs(hostAddressIp.hashCode()) % 1024 + ""; 26 | System.setProperty("workerId", workerId); 27 | } catch (UnknownHostException e) { 28 | throw new RuntimeException(e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.minilink.config; 2 | 3 | import com.minilink.constant.CommonConstant; 4 | import com.minilink.constant.RegexConstant; 5 | import io.swagger.v3.oas.models.Components; 6 | import io.swagger.v3.oas.models.OpenAPI; 7 | import io.swagger.v3.oas.models.info.Contact; 8 | import io.swagger.v3.oas.models.info.Info; 9 | import io.swagger.v3.oas.models.info.License; 10 | import io.swagger.v3.oas.models.security.SecurityRequirement; 11 | import io.swagger.v3.oas.models.security.SecurityScheme; 12 | import org.springdoc.core.models.GroupedOpenApi; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | 16 | /** 17 | * @Author: 徐志斌 18 | * @CreateTime: 2024-12-20 16:14 19 | * @Description: Swagger 配置类 20 | * @Version: 1.0 21 | */ 22 | @Configuration 23 | public class SwaggerConfig { 24 | @Bean 25 | public GroupedOpenApi usersGroup() { 26 | return GroupedOpenApi.builder() 27 | .group("users") 28 | .addOperationCustomizer((operation, handlerMethod) -> { 29 | operation.addSecurityItem(new SecurityRequirement().addList(CommonConstant.HEADER_TOKEN_KEY)); 30 | return operation; 31 | }) 32 | .build(); 33 | } 34 | 35 | @Bean 36 | public OpenAPI customOpenAPI() { 37 | Components components = new Components(); 38 | components.addSecuritySchemes(CommonConstant.HEADER_TOKEN_KEY, 39 | new SecurityScheme() 40 | .type(SecurityScheme.Type.APIKEY) 41 | .scheme("basic") 42 | .name(CommonConstant.HEADER_TOKEN_KEY) 43 | .in(SecurityScheme.In.HEADER) 44 | .description("请求头") 45 | ); 46 | return new OpenAPI() 47 | .components(components) 48 | .info(apiInfo()); 49 | } 50 | 51 | private Info apiInfo() { 52 | Contact contact = new Contact(); 53 | contact.setEmail("binx19980707@gmail.com"); 54 | contact.setName("徐志斌"); 55 | contact.setUrl("https://xuzhibin.blog.csdn.net"); 56 | return new Info() 57 | .title("Mini Link 接口文档") 58 | .version("1.0") 59 | .contact(contact) 60 | .license(new License().name("Apache 2.0").url("http://springdoc.org")); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/constant/CommonConstant.java: -------------------------------------------------------------------------------- 1 | package com.minilink.constant; 2 | 3 | /** 4 | * @Author: 徐志斌 5 | * @CreateTime: 2024-12-25 16:29 6 | * @Description: TODO 7 | * @Version: 1.0 8 | */ 9 | public class CommonConstant { 10 | public static final String HEADER_TOKEN_KEY = "mini-link-token"; 11 | } 12 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/constant/KafkaConstant.java: -------------------------------------------------------------------------------- 1 | package com.minilink.constant; 2 | 3 | /** 4 | * @Author: 徐志斌 5 | * @CreateTime: 2024-12-20 10:49 6 | * @Description: Kafka 常量 7 | * @Version: 1.0 8 | */ 9 | public class KafkaConstant { 10 | public static final String ODS_CLICK_LINK_TOPIC = "ods_click_link_topic"; 11 | } 12 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/constant/RedisConstant.java: -------------------------------------------------------------------------------- 1 | package com.minilink.constant; 2 | 3 | /** 4 | * @Author 徐志斌 5 | * @Date: 2024/12/7 9:14 6 | * @Version 1.0 7 | * @Description: Redis Key常量 8 | */ 9 | public class RedisConstant { 10 | /** 11 | * 验证码 Key 12 | */ 13 | public static final String CAPTCHA_KEY = "account:captcha"; 14 | public static final String EMAIL_CODE_KEY = "account:email-code"; 15 | public static final String EMAIL_CHECK_KEY = "account:email-check"; 16 | 17 | /** 18 | * Spring Cache Key 19 | */ 20 | public static final String CACHE_LINK_USER = "cache:link-user"; 21 | public static final String CACHE_LINK_URL_TOB = "cache:link-url-tob"; 22 | public static final String CACHE_LINK_URL_TOC = "cache:link-url-toc"; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/constant/RegexConstant.java: -------------------------------------------------------------------------------- 1 | package com.minilink.constant; 2 | 3 | /** 4 | * @Author: 徐志斌 5 | * @CreateTime: 2024-12-20 14:08 6 | * @Description: 正则表达式-常量 7 | * @Version: 1.0 8 | */ 9 | public class RegexConstant { 10 | public static final String REGEX_SHORT_LINK_FORMAT = "^\\d+-\\d+-[a-z0-9A-Z]+$"; 11 | public static final String REGEX_LONG_LINK_FORMAT = "^https?://.*"; 12 | public static final String REGEX_EMAIL_FORMAT = "[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+"; 13 | } 14 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/enums/BusinessCodeEnum.java: -------------------------------------------------------------------------------- 1 | package com.minilink.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * @Author 徐志斌 8 | * @Date: 2024/12/4 10:53 9 | * @Version 1.0 10 | * @Description: 响应、异常状态码枚举 11 | */ 12 | @Getter 13 | @AllArgsConstructor 14 | public enum BusinessCodeEnum { 15 | /** 16 | * 通用操作码 17 | */ 18 | SUCCESS(20000, "操作成功"), 19 | FAIL(50000, "操作失败"), 20 | PARAM_ERROR(50001, "服务端参数异常"), 21 | OPS_REPEAT(110001, "重复操作"), 22 | 23 | /** 24 | * 验证码 25 | */ 26 | CODE_LIMITED(240002, "验证码发送过快"), 27 | CODE_EMAIL_ERROR(240003, "邮箱验证码错误"), 28 | CODE_CAPTCHA_ERROR(240101, "图形验证码错误"), 29 | 30 | /** 31 | * 账号 32 | */ 33 | ACCOUNT_REPEAT(250001, "账号已经存在"), 34 | ACCOUNT_UNREGISTER(250002, "账号不存在"), 35 | ACCOUNT_PWD_ERROR(250003, "账号或密码错误"), 36 | ACCOUNT_NO_LOGIN(250004, "账号未登录"), 37 | PASSWORD_NO_EQUAL(250005, "两次密码输入不一致"), 38 | 39 | /** 40 | * 短链接 41 | */ 42 | SHORT_LINK_NOT_EXIST(260404, "短链接不存在"), 43 | SHORT_LINK_EXPIRED(260001, "短链接已过期,无法访问"), 44 | SHORT_LINK_REPEAT(260501, "短链接重复,请重新生成"), 45 | 46 | /** 47 | * 长链接 48 | */ 49 | LONG_LINK_NOT_EXIST(270404, "长链接不存在"), 50 | 51 | /** 52 | * 流控操作 53 | */ 54 | CONTROL_FLOW(500101, "限流控制"), 55 | CONTROL_DEGRADE(500201, "降级控制"), 56 | CONTROL_AUTH(500301, "认证控制"), 57 | 58 | /** 59 | * 文件相关 60 | */ 61 | FILE_UPLOAD_USER_IMG_FAIL(700101, "⽤户头像⽂件上传失败"), 62 | 63 | /** 64 | * 正则表达式 65 | */ 66 | REGEX_EMAIL_FORMAT_ERROR(900001, "邮箱格式错误"), 67 | REGEX_SHORT_LINK_FORMAT_ERROR(900002, "短链接生成格式错误"), 68 | REGEX_LONG_LINK_FORMAT_ERROR(900003, "长链接格式错误"), 69 | ; 70 | 71 | private int code; 72 | private String msg; 73 | } -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/enums/EmailEnum.java: -------------------------------------------------------------------------------- 1 | package com.minilink.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * @Author: 徐志斌 8 | * @CreateTime: 2024-12-23 09:57 9 | * @Description: 邮件枚举 10 | * @Version: 1.0 11 | */ 12 | @Getter 13 | @AllArgsConstructor 14 | public enum EmailEnum { 15 | REGISTER(1, "注册表单邮件"), 16 | ; 17 | 18 | private int code; 19 | private String msg; 20 | } 21 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package com.minilink.exception; 2 | 3 | import com.minilink.enums.BusinessCodeEnum; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | 7 | /** 8 | * @Author 徐志斌 9 | * @Date: 2024/12/4 10:52 10 | * @Version 1.0 11 | * @Description: 自定义业务异常 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | public class BusinessException extends RuntimeException { 16 | private BusinessCodeEnum codeEnum; 17 | } -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/interceptor/LoginInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.minilink.interceptor; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; 4 | import com.minilink.annotation.NoLogin; 5 | import com.minilink.constant.CommonConstant; 6 | import com.minilink.enums.BusinessCodeEnum; 7 | import com.minilink.exception.BusinessException; 8 | import com.minilink.pojo.po.LinkUser; 9 | import com.minilink.util.JwtUtil; 10 | import jakarta.servlet.http.HttpServletRequest; 11 | import jakarta.servlet.http.HttpServletResponse; 12 | import org.springframework.http.HttpMethod; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.stereotype.Component; 15 | import org.springframework.web.method.HandlerMethod; 16 | import org.springframework.web.servlet.HandlerInterceptor; 17 | import org.springframework.web.servlet.ModelAndView; 18 | 19 | /** 20 | * @Author 徐志斌 21 | * @Date: 2024/12/8 17:05 22 | * @Version 1.0 23 | * @Description: 登录Token-拦截器 24 | */ 25 | @Component 26 | public class LoginInterceptor implements HandlerInterceptor { 27 | public static ThreadLocal threadLocal = new ThreadLocal<>(); 28 | 29 | @Override 30 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { 31 | if (HttpMethod.OPTIONS.toString().equalsIgnoreCase(request.getMethod())) { 32 | response.setStatus(HttpStatus.NO_CONTENT.value()); 33 | return true; 34 | } 35 | HandlerMethod method = (HandlerMethod) handler; 36 | NoLogin noLogin = method.getMethod().getAnnotation(NoLogin.class); 37 | if (ObjectUtils.isNotEmpty(noLogin)) { 38 | return true; 39 | } 40 | String token = request.getHeader(CommonConstant.HEADER_TOKEN_KEY); 41 | if (ObjectUtils.isEmpty(token)) { 42 | throw new BusinessException(BusinessCodeEnum.ACCOUNT_NO_LOGIN); 43 | } 44 | LinkUser user = JwtUtil.resolve(token); 45 | if (ObjectUtils.isEmpty(user)) { 46 | throw new BusinessException(BusinessCodeEnum.ACCOUNT_NO_LOGIN); 47 | } 48 | threadLocal.set(user); 49 | return true; 50 | } 51 | 52 | @Override 53 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { 54 | } 55 | 56 | @Override 57 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { 58 | threadLocal.remove(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/interceptor/LoginInterceptorHandler.java: -------------------------------------------------------------------------------- 1 | package com.minilink.interceptor; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | /** 12 | * @Author 徐志斌 13 | * @Date: 2024/12/8 17:22 14 | * @Version 1.0 15 | * @Description: Token 拦截器配置 16 | */ 17 | @Configuration 18 | public class LoginInterceptorHandler implements WebMvcConfigurer { 19 | @Autowired 20 | private LoginInterceptor loginInterceptor; 21 | private static final List SWAGGER_EXCLUDE_PATH = Arrays.asList( 22 | "/doc.html", 23 | "/swagger**/**", 24 | "/swagger-resources/**", 25 | "/webjars/**", 26 | "/v3/**" 27 | ); 28 | 29 | @Override 30 | public void addInterceptors(InterceptorRegistry registry) { 31 | registry.addInterceptor(loginInterceptor) 32 | .addPathPatterns("/**") 33 | .excludePathPatterns(SWAGGER_EXCLUDE_PATH); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/pojo/dto/LinkUrlSaveDTO.java: -------------------------------------------------------------------------------- 1 | package com.minilink.pojo.dto; 2 | 3 | import com.minilink.constant.RegexConstant; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Pattern; 6 | import lombok.Data; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | /** 11 | * @Author: 徐志斌 12 | * @CreateTime: 2024-12-13 13:28 13 | * @Description: 链接保存入参DTO 14 | * @Version: 1.0 15 | */ 16 | @Data 17 | public class LinkUrlSaveDTO { 18 | /** 19 | * 分组id 20 | */ 21 | private Long groupId; 22 | 23 | /** 24 | * 链接 icon 25 | */ 26 | private String icon; 27 | 28 | /** 29 | * 标题 30 | */ 31 | @NotBlank(message = "标题信息不能为空") 32 | private String title; 33 | 34 | /** 35 | * 长链接(目标链接) 36 | */ 37 | @NotBlank(message = "长链接信息不能为空") 38 | @Pattern(regexp = RegexConstant.REGEX_LONG_LINK_FORMAT, message = "长链接格式不正确") 39 | private String longLink; 40 | 41 | /** 42 | * 到期时间 43 | */ 44 | private LocalDateTime expiredTime; 45 | } 46 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/pojo/dto/LoginDTO.java: -------------------------------------------------------------------------------- 1 | package com.minilink.pojo.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @Author 徐志斌 7 | * @Date: 2024/12/7 21:42 8 | * @Version 1.0 9 | * @Description: 登录表单DTO 10 | */ 11 | @Data 12 | public class LoginDTO { 13 | /** 14 | * 邮箱 15 | */ 16 | private String email; 17 | 18 | /** 19 | * 输入密码 20 | */ 21 | private String password; 22 | 23 | /** 24 | * 图片验证码 25 | */ 26 | private String captchaCode; 27 | } 28 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/pojo/dto/RegisterDTO.java: -------------------------------------------------------------------------------- 1 | package com.minilink.pojo.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @Author 徐志斌 7 | * @Date: 2024/12/7 21:31 8 | * @Version 1.0 9 | * @Description: 注册表单DTO 10 | */ 11 | @Data 12 | public class RegisterDTO { 13 | /** 14 | * 邮箱 15 | */ 16 | private String email; 17 | 18 | /** 19 | * 邮箱验证码 20 | */ 21 | private String emailCode; 22 | 23 | /** 24 | * 输入密码 25 | */ 26 | private String password1; 27 | 28 | /** 29 | * 确认密码 30 | */ 31 | private String password2; 32 | } 33 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/pojo/entity/EmailParamEntity.java: -------------------------------------------------------------------------------- 1 | package com.minilink.pojo.entity; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @Author: 徐志斌 7 | * @CreateTime: 2024-12-23 10:13 8 | * @Description: 发送邮件参数 9 | * @Version: 1.0 10 | */ 11 | @Data 12 | public class EmailParamEntity { 13 | /** 14 | * 接收人邮件 15 | */ 16 | private String email; 17 | 18 | /** 19 | * 邮件验证码 20 | */ 21 | private String emailCode; 22 | } 23 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/pojo/entity/VisitShortLinkMsg.java: -------------------------------------------------------------------------------- 1 | package com.minilink.pojo.entity; 2 | 3 | import lombok.Data; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | /** 8 | * @Author: 徐志斌 9 | * @CreateTime: 2024-12-20 11:21 10 | * @Description: 访问短链接-队列消息 11 | * @Version: 1.0 12 | */ 13 | @Data 14 | public class VisitShortLinkMsg { 15 | /** 16 | * 账号id 17 | */ 18 | private Long accountId; 19 | 20 | /** 21 | * 访问ip 22 | */ 23 | private String ip; 24 | 25 | /** 26 | * 浏览器指纹 27 | */ 28 | private String userAgent; 29 | 30 | /** 31 | * 短链接码 32 | */ 33 | private String shortLinkCode; 34 | 35 | /** 36 | * 访问时间 37 | */ 38 | private LocalDateTime visitTime; 39 | } 40 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/pojo/po/LinkGroup.java: -------------------------------------------------------------------------------- 1 | package com.minilink.pojo.po; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.experimental.Accessors; 7 | 8 | import java.io.Serializable; 9 | import java.time.LocalDateTime; 10 | 11 | /** 12 | *

13 | * 14 | *

15 | * 16 | * @author 徐志斌 17 | * @since 2024-12-12 18 | */ 19 | @Getter 20 | @Setter 21 | @Accessors(chain = true) 22 | @TableName("link_group") 23 | public class LinkGroup implements Serializable { 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | /** 28 | * 主键 29 | */ 30 | @TableId(value = "id", type = IdType.ASSIGN_ID) 31 | private Long id; 32 | 33 | /** 34 | * 账号 35 | */ 36 | @TableField("account_id") 37 | private Long accountId; 38 | 39 | /** 40 | * 分组名称 41 | */ 42 | @TableField("group_name") 43 | private String groupName; 44 | 45 | /** 46 | * 创建时间 47 | */ 48 | @TableField(value = "create_time", fill = FieldFill.INSERT) 49 | private LocalDateTime createTime; 50 | 51 | /** 52 | * 修改时间 53 | */ 54 | @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) 55 | private LocalDateTime updateTime; 56 | 57 | /** 58 | * 删除标识 59 | */ 60 | @TableField(value = "deleted", fill = FieldFill.INSERT) 61 | @TableLogic 62 | private Boolean deleted; 63 | } 64 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/pojo/po/LinkUrlTob.java: -------------------------------------------------------------------------------- 1 | package com.minilink.pojo.po; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.experimental.Accessors; 7 | 8 | import java.io.Serializable; 9 | import java.time.LocalDateTime; 10 | 11 | /** 12 | *

13 | * 14 | *

15 | * 16 | * @author 徐志斌 17 | * @since 2024-12-09 18 | */ 19 | @Getter 20 | @Setter 21 | @Accessors(chain = true) 22 | @TableName("link_url_tob") 23 | public class LinkUrlTob implements Serializable { 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | /** 28 | * 主键id 29 | */ 30 | @TableId(value = "id") 31 | private Long id; 32 | 33 | /** 34 | * 分组id 35 | */ 36 | @TableField("group_id") 37 | private Long groupId; 38 | 39 | /** 40 | * 账号 41 | */ 42 | @TableField("account_id") 43 | private Long accountId; 44 | 45 | /** 46 | * 链接标题 47 | */ 48 | @TableField("title") 49 | private String title; 50 | 51 | /** 52 | * 链接icon 53 | */ 54 | @TableField("icon") 55 | private String icon; 56 | 57 | /** 58 | * 域名 59 | */ 60 | @TableField("domain") 61 | private String domain; 62 | 63 | /** 64 | * 短链接码(1-0-cXcLm) 65 | */ 66 | @TableField("short_link_code") 67 | private String shortLinkCode; 68 | 69 | /** 70 | * 短链接 71 | */ 72 | @TableField("short_link") 73 | private String shortLink; 74 | 75 | /** 76 | * 长链接 77 | */ 78 | @TableField("long_link") 79 | private String longLink; 80 | 81 | /** 82 | * 二维码 83 | */ 84 | @TableField("qr_code") 85 | private String qrCode; 86 | 87 | /** 88 | * 到期时间 89 | */ 90 | @TableField("expired_time") 91 | private LocalDateTime expiredTime; 92 | 93 | /** 94 | * 创建时间 95 | */ 96 | @TableField(value = "create_time", fill = FieldFill.INSERT) 97 | private LocalDateTime createTime; 98 | 99 | /** 100 | * 修改时间 101 | */ 102 | @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) 103 | private LocalDateTime updateTime; 104 | 105 | /** 106 | * 删除标识 107 | */ 108 | @TableField(value = "deleted", fill = FieldFill.INSERT) 109 | @TableLogic 110 | private Boolean deleted; 111 | } 112 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/pojo/po/LinkUrlToc.java: -------------------------------------------------------------------------------- 1 | package com.minilink.pojo.po; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.experimental.Accessors; 7 | 8 | import java.io.Serializable; 9 | import java.time.LocalDateTime; 10 | 11 | /** 12 | *

13 | * 14 | *

15 | * 16 | * @author 徐志斌 17 | * @since 2024-12-14 18 | */ 19 | @Getter 20 | @Setter 21 | @Accessors(chain = true) 22 | @TableName("link_url_toc") 23 | public class LinkUrlToc implements Serializable { 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | /** 28 | * 主键id 29 | */ 30 | @TableId(value = "id") 31 | private Long id; 32 | 33 | /** 34 | * 账号id 35 | */ 36 | @TableField(value = "account_id") 37 | private Long accountId; 38 | 39 | /** 40 | * 短链接码(1-0-cXcLm) 41 | */ 42 | @TableField("short_link_code") 43 | private String shortLinkCode; 44 | 45 | /** 46 | * 短链接 47 | */ 48 | @TableField("short_link") 49 | private String shortLink; 50 | 51 | /** 52 | * 长链接 53 | */ 54 | @TableField("long_link") 55 | private String longLink; 56 | 57 | /** 58 | * 到期时间 59 | */ 60 | @TableField("expired_time") 61 | private LocalDateTime expiredTime; 62 | 63 | /** 64 | * 创建时间 65 | */ 66 | @TableField(value = "create_time", fill = FieldFill.INSERT) 67 | private LocalDateTime createTime; 68 | 69 | /** 70 | * 修改时间 71 | */ 72 | @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) 73 | private LocalDateTime updateTime; 74 | 75 | /** 76 | * 删除标识 77 | */ 78 | @TableField(value = "deleted", fill = FieldFill.INSERT) 79 | @TableLogic 80 | private Boolean deleted; 81 | } 82 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/pojo/po/LinkUser.java: -------------------------------------------------------------------------------- 1 | package com.minilink.pojo.po; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.experimental.Accessors; 7 | 8 | import java.io.Serializable; 9 | import java.time.LocalDateTime; 10 | 11 | /** 12 | *

13 | * 14 | *

15 | * 16 | * @author 徐志斌 17 | * @since 2024-12-06 18 | */ 19 | @Getter 20 | @Setter 21 | @Accessors(chain = true) 22 | @TableName("link_user") 23 | public class LinkUser implements Serializable { 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | /** 28 | * 主键 29 | */ 30 | @TableId(value = "id") 31 | private Long id; 32 | 33 | /** 34 | * 账号 35 | */ 36 | @TableField("account_id") 37 | private Long accountId; 38 | 39 | /** 40 | * 邮箱 41 | */ 42 | @TableField("email") 43 | private String email; 44 | 45 | /** 46 | * 头像 47 | */ 48 | @TableField("avatar") 49 | private String avatar; 50 | 51 | /** 52 | * 昵称 53 | */ 54 | @TableField("nick_name") 55 | private String nickName; 56 | 57 | /** 58 | * 密码 59 | */ 60 | @TableField("password") 61 | private String password; 62 | 63 | /** 64 | * 盐 65 | */ 66 | @TableField("salt") 67 | private String salt; 68 | 69 | /** 70 | * 创建时间 71 | */ 72 | @TableField(value = "create_time", fill = FieldFill.INSERT) 73 | private LocalDateTime createTime; 74 | 75 | /** 76 | * 修改时间 77 | */ 78 | @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) 79 | private LocalDateTime updateTime; 80 | 81 | /** 82 | * 删除标识 83 | */ 84 | @TableField(value = "deleted", fill = FieldFill.INSERT) 85 | @TableLogic 86 | private Boolean deleted; 87 | } 88 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/pojo/vo/LinkUrlTobVO.java: -------------------------------------------------------------------------------- 1 | package com.minilink.pojo.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | /** 8 | * @Author 徐志斌 9 | * @Date: 2024/12/21 7:47 10 | * @Version 1.0 11 | * @Description: ToB短链接VO 12 | */ 13 | @Data 14 | public class LinkUrlTobVO { 15 | /** 16 | * 分组id 17 | */ 18 | private Long groupId; 19 | 20 | /** 21 | * 账号 22 | */ 23 | private Long accountId; 24 | 25 | /** 26 | * 链接标题 27 | */ 28 | private String title; 29 | 30 | /** 31 | * 链接icon 32 | */ 33 | private String icon; 34 | 35 | /** 36 | * 短链接 37 | */ 38 | private String shortLink; 39 | 40 | /** 41 | * 长链接 42 | */ 43 | private String longLink; 44 | 45 | /** 46 | * 二维码 47 | */ 48 | private String qrCode; 49 | 50 | /** 51 | * 到期时间 52 | */ 53 | private LocalDateTime expiredTime; 54 | 55 | /** 56 | * 创建时间 57 | */ 58 | private LocalDateTime createTime; 59 | } 60 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/pojo/vo/UserVO.java: -------------------------------------------------------------------------------- 1 | package com.minilink.pojo.vo; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableField; 4 | import lombok.Data; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * @Author 徐志斌 10 | * @Date: 2024/12/8 19:45 11 | * @Version 1.0 12 | * @Description: 账户信息VO 13 | */ 14 | @Data 15 | public class UserVO { 16 | /** 17 | * 账号 18 | */ 19 | @TableField("email") 20 | private String email; 21 | 22 | /** 23 | * 头像 24 | */ 25 | @TableField("avatar") 26 | private String avatar; 27 | 28 | /** 29 | * 昵称 30 | */ 31 | @TableField("nick_name") 32 | private String nickName; 33 | 34 | /** 35 | * 创建时间 36 | */ 37 | private LocalDateTime createTime; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/util/EncryptUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | 7 | /** 8 | * @Author 徐志斌 9 | * @Date: 2024/12/6 21:09 10 | * @Version 1.0 11 | * @Description: 加密工具类 12 | */ 13 | public class EncryptUtil { 14 | public static String md5(String data) throws NoSuchAlgorithmException, UnsupportedEncodingException { 15 | MessageDigest md = MessageDigest.getInstance("MD5"); 16 | byte[] array = md.digest(data.getBytes("UTF-8")); 17 | StringBuilder sb = new StringBuilder(); 18 | for (byte item : array) { 19 | sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3); 20 | } 21 | return sb.toString().toUpperCase(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/util/HttpServletUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import org.springframework.web.context.request.RequestContextHolder; 7 | import org.springframework.web.context.request.ServletRequestAttributes; 8 | 9 | /** 10 | * @Author: 徐志斌 11 | * @CreateTime: 2023-03-24 17:46 12 | * @Description: HttpServlet 工具类 13 | * @Version: 1.0 14 | */ 15 | public class HttpServletUtil { 16 | private static ServletRequestAttributes getRequestAttributes() { 17 | return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 18 | } 19 | 20 | public static HttpServletRequest getRequest() { 21 | return getRequestAttributes().getRequest(); 22 | } 23 | 24 | public static HttpServletResponse getResponse() { 25 | return getRequestAttributes().getResponse(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/util/IpUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | 5 | /** 6 | * @Author: 徐志斌 7 | * @CreateTime: 2024-12-26 13:31 8 | * @Description: Ip地址工具类 9 | * @Version: 1.0 10 | */ 11 | public class IpUtil { 12 | public static String getIpAddr() { 13 | HttpServletRequest request = HttpServletUtil.getRequest(); 14 | String ip = request.getHeader("x-forwarded-for"); 15 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 16 | ip = request.getHeader("X-Real-IP"); 17 | } 18 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 19 | ip = request.getHeader("http_client_ip"); 20 | } 21 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 22 | ip = request.getRemoteAddr(); 23 | } 24 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 25 | ip = request.getHeader("Proxy-Client-IP"); 26 | } 27 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 28 | ip = request.getHeader("WL-Proxy-Client-IP"); 29 | } 30 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 31 | ip = request.getHeader("HTTP_X_FORWARDED_FOR"); 32 | } 33 | if (ip != null && ip.indexOf(",") != -1) { 34 | ip = ip.substring(ip.lastIndexOf(",") + 1).trim(); 35 | } 36 | return ip; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/util/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 4 | import com.minilink.adapter.UserAdapter; 5 | import com.minilink.pojo.po.LinkUser; 6 | import io.jsonwebtoken.Claims; 7 | import io.jsonwebtoken.Jws; 8 | import io.jsonwebtoken.Jwts; 9 | import io.jsonwebtoken.SignatureAlgorithm; 10 | 11 | import java.util.Date; 12 | 13 | /** 14 | * @Author 徐志斌 15 | * @Date: 2023/11/13 21:46 16 | * @Version 1.0 17 | * @Description: JWT 工具类 18 | */ 19 | public class JwtUtil { 20 | public static final long EXPIRE = 7 * 24 * 60 * 60 * 1000; 21 | public static final String JWT_SECRET = "Mini_Link_XzbWsx"; 22 | 23 | public static String generate(Long accountId, String email, String nickName, String avatar) { 24 | return Jwts.builder() 25 | .setSubject("Mini Link") 26 | .setIssuedAt(new Date()) 27 | .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) 28 | .signWith(SignatureAlgorithm.HS256, JWT_SECRET) 29 | .claim("account_id", accountId) 30 | .claim("email", email) 31 | .claim("nick_name", nickName) 32 | .claim("avatar", avatar) 33 | .compact(); 34 | } 35 | 36 | public static LinkUser resolve(String token) { 37 | if (StringUtils.isBlank(token)) { 38 | return null; 39 | } 40 | Jws claimsJws = Jwts.parser() 41 | .setSigningKey(JWT_SECRET) 42 | .parseClaimsJws(token); 43 | Claims claims = claimsJws.getBody(); 44 | Long accountId = (Long) claims.get("account_id"); 45 | String email = (String) claims.get("email"); 46 | String nickName = (String) claims.get("nick_name"); 47 | String avatar = (String) claims.get("avatar"); 48 | return UserAdapter.buildUserPO(accountId, email, avatar, nickName, null, null); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/util/RandomUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * @Author 徐志斌 7 | * @Date: 2023/11/25 15:02 8 | * @Version 1.0 9 | * @Description: 随机生成工具类 10 | */ 11 | public class RandomUtil { 12 | public static final String NUM = "0123456789"; 13 | public static final String TEXT = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 14 | public static final String NUM_TEXT = NUM + TEXT; 15 | 16 | /** 17 | * 生成随机内容 18 | * 19 | * @param length 内容长度 20 | * @param type 内容类型(1:数字,2:字母,3:数字 + 字母) 21 | * @return 生成内容 22 | */ 23 | public static String generate(int length, int type) { 24 | StringBuilder code = new StringBuilder(); 25 | Random random = new Random(); 26 | String charSet = ""; 27 | switch (type) { 28 | case 1: 29 | charSet = NUM; 30 | break; 31 | case 2: 32 | charSet = TEXT; 33 | break; 34 | case 3: 35 | charSet = NUM_TEXT; 36 | break; 37 | default: 38 | return "Invalid type"; 39 | } 40 | for (int i = 0; i < length; i++) { 41 | code.append(charSet.charAt(random.nextInt(charSet.length()))); 42 | } 43 | return code.toString(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/util/SnowFlakeUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | import java.lang.management.ManagementFactory; 4 | import java.lang.management.RuntimeMXBean; 5 | import java.net.NetworkInterface; 6 | import java.net.SocketException; 7 | import java.util.Enumeration; 8 | 9 | /** 10 | * @Author: 徐志斌 11 | * @CreateTime: 2024-12-19 11:22 12 | * @Description: SnowFlake 雪花算法工具类 13 | * @Version: 1.0 14 | */ 15 | public class SnowFlakeUtil { 16 | private final static long twepoch = 12888349746579L; 17 | private final static long workerIdBits = 5L; 18 | private final static long datacenterIdBits = 5L; 19 | private final static long sequenceBits = 12L; 20 | private final static long workerIdShift = sequenceBits; 21 | private final static long datacenterIdShift = sequenceBits + workerIdBits; 22 | private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; 23 | private final static long sequenceMask = -1L ^ (-1L << sequenceBits); 24 | private static long lastTimestamp = -1L; 25 | private long sequence = 0L; 26 | private long workerId = 1L; 27 | private static long workerMask = -1L ^ (-1L << workerIdBits); 28 | private long processId = 1L; 29 | private static long processMask = -1L ^ (-1L << datacenterIdBits); 30 | private static SnowFlakeUtil snowFlake = null; 31 | 32 | static { 33 | snowFlake = new SnowFlakeUtil(); 34 | } 35 | 36 | public static synchronized long nextId() { 37 | return snowFlake.getNextId(); 38 | } 39 | 40 | private SnowFlakeUtil() { 41 | this.workerId = this.getMachineNum(); 42 | RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); 43 | this.processId = Long.valueOf(runtimeMXBean.getName().split("@")[0]).longValue(); 44 | this.workerId = workerId & workerMask; 45 | this.processId = processId & processMask; 46 | } 47 | 48 | public synchronized long getNextId() { 49 | long timestamp = timeGen(); 50 | if (timestamp < lastTimestamp) { 51 | try { 52 | throw new Exception("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds"); 53 | } catch (Exception e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | if (lastTimestamp == timestamp) { 58 | sequence = (sequence + 1) & sequenceMask; 59 | if (sequence == 0) { 60 | timestamp = tilNextMillis(lastTimestamp); 61 | } 62 | } else { 63 | sequence = 0; 64 | } 65 | lastTimestamp = timestamp; 66 | long nextId = ((timestamp - twepoch) << timestampLeftShift) | (processId << datacenterIdShift) | (workerId << workerIdShift) | sequence; 67 | return nextId; 68 | } 69 | 70 | private long tilNextMillis(final long lastTimestamp) { 71 | long timestamp = this.timeGen(); 72 | while (timestamp <= lastTimestamp) { 73 | timestamp = this.timeGen(); 74 | } 75 | return timestamp; 76 | } 77 | 78 | private long timeGen() { 79 | return System.currentTimeMillis(); 80 | } 81 | 82 | private long getMachineNum() { 83 | long machinePiece; 84 | StringBuilder sb = new StringBuilder(); 85 | Enumeration e = null; 86 | try { 87 | e = NetworkInterface.getNetworkInterfaces(); 88 | } catch (SocketException e1) { 89 | e1.printStackTrace(); 90 | } 91 | while (e.hasMoreElements()) { 92 | NetworkInterface ni = e.nextElement(); 93 | sb.append(ni.toString()); 94 | } 95 | machinePiece = sb.toString().hashCode(); 96 | return machinePiece; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/util/resp/BaseResponse.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util.resp; 2 | 3 | import com.minilink.enums.BusinessCodeEnum; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | /** 8 | * @Author 徐志斌 9 | * @Date: 2024/12/6 21:59 10 | * @Version 1.0 11 | * @Description: 响应基础类 12 | */ 13 | @Getter 14 | @Setter 15 | public class BaseResponse { 16 | private Integer code; 17 | private String msg; 18 | 19 | protected BaseResponse(BusinessCodeEnum codeEnum) { 20 | this.code = codeEnum.getCode(); 21 | this.msg = codeEnum.getMsg(); 22 | } 23 | } -------------------------------------------------------------------------------- /mini-link-common/src/main/java/com/minilink/util/resp/R.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util.resp; 2 | 3 | import com.minilink.enums.BusinessCodeEnum; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | /** 8 | * @Author 徐志斌 9 | * @Date: 2024/12/6 21:59 10 | * @Version 1.0 11 | * @Description: 响应封装类 12 | */ 13 | @Getter 14 | @Setter 15 | public class R extends BaseResponse { 16 | private T data; 17 | 18 | private R(BusinessCodeEnum codeEnum) { 19 | super(codeEnum); 20 | } 21 | 22 | private R(BusinessCodeEnum codeEnum, T data) { 23 | super(codeEnum); 24 | this.data = data; 25 | } 26 | 27 | public static R out(BusinessCodeEnum codeEnum) { 28 | return new R<>(codeEnum); 29 | } 30 | 31 | public static R out(BusinessCodeEnum codeEnum, T data) { 32 | return new R<>(codeEnum, data); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mini-link-core/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:17 2 | MAINTAINER 徐志斌 3 | ADD mini-link-core.jar /mini-link-core.jar 4 | EXPOSE 9001 5 | ENTRYPOINT ["java", "-jar", "mini-link-core.jar"] -------------------------------------------------------------------------------- /mini-link-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | com.minilink 6 | mini-link 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | 4.0.0 11 | com.minilink 12 | mini-link-core 13 | 0.0.1-SNAPSHOT 14 | mini-link-core 15 | 16 | 17 | 18 | com.minilink 19 | mini-link-common 20 | 0.0.1-SNAPSHOT 21 | 22 | 23 | com.baomidou 24 | mybatis-plus-generator 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-freemarker 29 | 30 | 31 | 32 | 33 | org.apache.shardingsphere 34 | shardingsphere-jdbc 35 | 36 | 37 | org.jsoup 38 | jsoup 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-maven-plugin 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/MiniLinkCoreApplication.java: -------------------------------------------------------------------------------- 1 | package com.minilink; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cache.annotation.EnableCaching; 7 | 8 | @EnableCaching 9 | @SpringBootApplication 10 | @MapperScan("com.minilink.mapper") 11 | public class MiniLinkCoreApplication { 12 | public static void main(String[] args) { 13 | SpringApplication.run(MiniLinkCoreApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/controller/LinkGroupController.java: -------------------------------------------------------------------------------- 1 | package com.minilink.controller; 2 | 3 | import com.minilink.enums.BusinessCodeEnum; 4 | import com.minilink.util.resp.R; 5 | import io.swagger.v3.oas.annotations.Operation; 6 | import io.swagger.v3.oas.annotations.tags.Tag; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | /** 10 | * @Author: 徐志斌 11 | * @CreateTime: 2024-12-04 16:16 12 | * @Description: 链接分组 13 | * @Version: 1.0 14 | */ 15 | @Tag(name = "链接分组") 16 | @RestController 17 | @RequestMapping("/url/group") 18 | public class LinkGroupController { 19 | @Operation(summary = "创建分组") 20 | @PostMapping("/create") 21 | public R createGroup() { 22 | return R.out(BusinessCodeEnum.SUCCESS); 23 | } 24 | 25 | @Operation(summary = "查询分组列表") 26 | @GetMapping("/list") 27 | public R getGroupList() { 28 | return R.out(BusinessCodeEnum.SUCCESS); 29 | } 30 | 31 | @Operation(summary = "修改分组") 32 | @PostMapping("/update") 33 | public R updateGroup() { 34 | return R.out(BusinessCodeEnum.SUCCESS); 35 | } 36 | 37 | @Operation(summary = "删除分组") 38 | @DeleteMapping("/delete/{groupId}") 39 | public R deleteGroup(@PathVariable String groupId) { 40 | return R.out(BusinessCodeEnum.SUCCESS); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/controller/LinkUrlTobController.java: -------------------------------------------------------------------------------- 1 | package com.minilink.controller; 2 | 3 | import com.minilink.enums.BusinessCodeEnum; 4 | import com.minilink.pojo.vo.LinkUrlTobVO; 5 | import com.minilink.pojo.dto.LinkUrlSaveDTO; 6 | import com.minilink.service.LinkUrlTobService; 7 | import com.minilink.util.resp.R; 8 | import io.swagger.v3.oas.annotations.Operation; 9 | import io.swagger.v3.oas.annotations.tags.Tag; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.validation.annotation.Validated; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import java.io.IOException; 15 | import java.util.Map; 16 | 17 | /** 18 | * @Author: 徐志斌 19 | * @CreateTime: 2024-12-03 16:37 20 | * @Description: B端-短链接 21 | * @Version: 1.0 22 | */ 23 | @Tag(name = "B端-短链接") 24 | @RestController 25 | @RequestMapping("/url") 26 | public class LinkUrlTobController { 27 | @Autowired 28 | private LinkUrlTobService urlTobService; 29 | 30 | @Operation(summary = "创建链接") 31 | @PostMapping("/create") 32 | public R create(@Validated @RequestBody LinkUrlSaveDTO saveDTO) { 33 | urlTobService.createShortLink(saveDTO); 34 | return R.out(BusinessCodeEnum.SUCCESS); 35 | } 36 | 37 | @Operation(summary = "解析链接内容") 38 | @GetMapping("/parse") 39 | public R parse(String link) throws IOException { 40 | Map resultMap = urlTobService.parseLink(link); 41 | return R.out(BusinessCodeEnum.SUCCESS, resultMap); 42 | } 43 | 44 | @Operation(summary = "分页列表") 45 | @GetMapping("/page/{groupId}/{current}/{size}") 46 | public R getPageList(@PathVariable Long groupId, 47 | @PathVariable Integer current, 48 | @PathVariable Integer size) { 49 | Map resultMap = urlTobService.getPageList(groupId, current, size); 50 | return R.out(BusinessCodeEnum.SUCCESS, resultMap); 51 | } 52 | 53 | @Operation(summary = "链接详情") 54 | @GetMapping("/detail/{id}") 55 | public R detail(@PathVariable Long id) { 56 | LinkUrlTobVO result = urlTobService.detail(id); 57 | return R.out(BusinessCodeEnum.SUCCESS, result); 58 | } 59 | 60 | @Operation(summary = "修改链接") 61 | @PostMapping("/update") 62 | public R update() { 63 | return R.out(BusinessCodeEnum.SUCCESS); 64 | } 65 | 66 | @Operation(summary = "删除链接") 67 | @DeleteMapping("/delete/{id}") 68 | public R delete(@PathVariable Long id) { 69 | urlTobService.deleteUrl(id); 70 | return R.out(BusinessCodeEnum.SUCCESS); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/controller/LinkUrlTocController.java: -------------------------------------------------------------------------------- 1 | package com.minilink.controller; 2 | 3 | import com.minilink.annotation.NoLogin; 4 | import com.minilink.service.LinkUrlTocService; 5 | import io.swagger.v3.oas.annotations.Operation; 6 | import io.swagger.v3.oas.annotations.tags.Tag; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | /** 13 | * @Author: 徐志斌 14 | * @CreateTime: 2024-12-13 13:42 15 | * @Description: C端-短链接 16 | * @Version: 1.0 17 | */ 18 | @Tag(name = "C端-短链接") 19 | @RestController 20 | public class LinkUrlTocController { 21 | @Autowired 22 | private LinkUrlTocService tocService; 23 | 24 | @NoLogin 25 | @Operation(summary = "访问短链接") 26 | @GetMapping("/{shortLinkCode}") 27 | public void redirect(@PathVariable String shortLinkCode) { 28 | tocService.redirect(shortLinkCode); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/mapper/LinkGroupMapper.java: -------------------------------------------------------------------------------- 1 | package com.minilink.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.minilink.pojo.po.LinkGroup; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | * @author 徐志斌 12 | * @since 2024-12-12 13 | */ 14 | public interface LinkGroupMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/mapper/LinkUrlTobMapper.java: -------------------------------------------------------------------------------- 1 | package com.minilink.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.minilink.pojo.po.LinkUrlTob; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | * @author 徐志斌 12 | * @since 2024-12-09 13 | */ 14 | public interface LinkUrlTobMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/mapper/LinkUrlTocMapper.java: -------------------------------------------------------------------------------- 1 | package com.minilink.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.minilink.pojo.po.LinkUrlToc; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | * @author 徐志斌 12 | * @since 2024-12-14 13 | */ 14 | public interface LinkUrlTocMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/service/LinkGroupService.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service; 2 | 3 | /** 4 | *

5 | * 服务类 6 | *

7 | * 8 | * @author 徐志斌 9 | * @since 2024-12-12 10 | */ 11 | public interface LinkGroupService { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/service/LinkUrlTobService.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service; 2 | 3 | import com.minilink.pojo.dto.LinkUrlSaveDTO; 4 | import com.minilink.pojo.vo.LinkUrlTobVO; 5 | 6 | import java.io.IOException; 7 | import java.util.Map; 8 | 9 | /** 10 | *

11 | * 服务类 12 | *

13 | * 14 | * @author 徐志斌 15 | * @since 2024-12-09 16 | */ 17 | public interface LinkUrlTobService { 18 | /** 19 | * 生成短链接 20 | * 21 | * @param saveDTO 保存入参DTO 22 | */ 23 | void createShortLink(LinkUrlSaveDTO saveDTO); 24 | 25 | /** 26 | * 获取目标链接 User-Agent 信息 27 | * 28 | * @param link 目标链接 29 | * @return User-Agent信息 30 | */ 31 | Map parseLink(String link) throws IOException; 32 | 33 | /** 34 | * 分页查询短链接列表 35 | * 36 | * @param groupId 分组id 37 | * @param current 当前页 38 | * @param size 每页数据条数 39 | * @return 40 | */ 41 | Map getPageList(Long groupId, Integer current, Integer size); 42 | 43 | /** 44 | * 根据 id 查询短链接详情 45 | * 46 | * @param id 主键id 47 | * @return 链接详情 48 | */ 49 | LinkUrlTobVO detail(Long id); 50 | 51 | /** 52 | * 根据 id 删除短链接 53 | * 54 | * @param id 目标id 55 | */ 56 | void deleteUrl(Long id); 57 | } 58 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/service/LinkUrlTocService.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service; 2 | 3 | /** 4 | *

5 | * 服务类 6 | *

7 | * 8 | * @author 徐志斌 9 | * @since 2024-12-14 10 | */ 11 | public interface LinkUrlTocService { 12 | /** 13 | * 访问短链接 14 | * 15 | * @param shortLinkCode 短链接码 16 | */ 17 | void redirect(String shortLinkCode); 18 | } 19 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/service/impl/LinkGroupServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service.impl; 2 | 3 | import com.minilink.service.LinkGroupService; 4 | import org.springframework.stereotype.Service; 5 | 6 | /** 7 | *

8 | * 服务实现类 9 | *

10 | * 11 | * @author 徐志斌 12 | * @since 2024-12-12 13 | */ 14 | @Service 15 | public class LinkGroupServiceImpl implements LinkGroupService { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/service/impl/LinkUrlTobServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; 4 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 5 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 6 | import com.minilink.adapter.LinkUrlAdapter; 7 | import com.minilink.constant.RegexConstant; 8 | import com.minilink.enums.BusinessCodeEnum; 9 | import com.minilink.exception.BusinessException; 10 | import com.minilink.interceptor.LoginInterceptor; 11 | import com.minilink.pojo.dto.LinkUrlSaveDTO; 12 | import com.minilink.pojo.po.LinkUrlTob; 13 | import com.minilink.pojo.po.LinkUrlToc; 14 | import com.minilink.pojo.vo.LinkUrlTobVO; 15 | import com.minilink.service.LinkUrlTobService; 16 | import com.minilink.store.LinkUrlTobStore; 17 | import com.minilink.store.LinkUrlTocStore; 18 | import com.minilink.util.LinkUrlUtil; 19 | import com.minilink.util.SnowFlakeUtil; 20 | import org.jsoup.Jsoup; 21 | import org.jsoup.nodes.Document; 22 | import org.jsoup.nodes.Element; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.beans.factory.annotation.Value; 25 | import org.springframework.stereotype.Service; 26 | import org.springframework.transaction.annotation.Transactional; 27 | 28 | import java.io.IOException; 29 | import java.util.HashMap; 30 | import java.util.List; 31 | import java.util.Map; 32 | 33 | /** 34 | *

35 | * 服务实现类 36 | *

37 | * 38 | * @author 徐志斌 39 | * @since 2024-12-09 40 | */ 41 | @Service 42 | public class LinkUrlTobServiceImpl implements LinkUrlTobService { 43 | @Autowired 44 | private LinkUrlTocStore urlTocStore; 45 | @Autowired 46 | private LinkUrlTobStore urlTobStore; 47 | 48 | @Value("${mini-link.group-id}") 49 | private Long miniLinkGroupId; 50 | @Value("${mini-link.domain}") 51 | private String miniLinkDomain; 52 | 53 | @Override 54 | @Transactional(rollbackFor = Exception.class) 55 | public void createShortLink(LinkUrlSaveDTO saveDTO) { 56 | String shortLinkCode = LinkUrlUtil.generate(SnowFlakeUtil.nextId() + "&" + saveDTO.getLongLink()); 57 | if (!shortLinkCode.matches(RegexConstant.REGEX_SHORT_LINK_FORMAT)) { 58 | throw new BusinessException(BusinessCodeEnum.REGEX_SHORT_LINK_FORMAT_ERROR); 59 | } 60 | LinkUrlToc shortLinkPO = urlTocStore.getByShortLinkCode(shortLinkCode); 61 | if (ObjectUtils.isNotEmpty(shortLinkPO)) { 62 | this.createShortLink(saveDTO); 63 | } 64 | LinkUrlTob tobLinkPO = LinkUrlAdapter.buildLinkUrlTobPO( 65 | LoginInterceptor.threadLocal.get().getAccountId(), 66 | ObjectUtils.isNotEmpty(saveDTO.getGroupId()) ? saveDTO.getGroupId() : miniLinkGroupId, 67 | saveDTO.getTitle(), 68 | saveDTO.getIcon(), 69 | miniLinkDomain, 70 | shortLinkCode, 71 | miniLinkDomain + shortLinkCode, 72 | saveDTO.getLongLink(), 73 | saveDTO.getExpiredTime() 74 | ); 75 | urlTobStore.saveLink(tobLinkPO); 76 | LinkUrlToc tocLinkPO = LinkUrlAdapter.buildLinkUrlTocPO( 77 | SnowFlakeUtil.nextId(), 78 | LoginInterceptor.threadLocal.get().getAccountId(), 79 | shortLinkCode, 80 | miniLinkDomain + shortLinkCode, 81 | saveDTO.getLongLink(), 82 | saveDTO.getExpiredTime() 83 | ); 84 | urlTocStore.saveLink(tocLinkPO); 85 | } 86 | 87 | @Override 88 | public Map parseLink(String link) throws IOException { 89 | if (StringUtils.isBlank(link) || !link.matches(RegexConstant.REGEX_LONG_LINK_FORMAT)) { 90 | return null; 91 | } 92 | Document doc = Jsoup.connect(link).get(); 93 | String title = doc.title(); 94 | Element descriptionTag = doc.select("meta[name=description]").first(); 95 | String description = descriptionTag != null ? descriptionTag.attr("content") : ""; 96 | Element iconTag = doc.select("link[rel=icon], link[rel=shortcut icon]").first(); 97 | String iconLink = iconTag != null ? iconTag.attr("href") : ""; 98 | Map resultMap = new HashMap<>(); 99 | resultMap.put("title", title); 100 | resultMap.put("desc", description); 101 | resultMap.put("icon", iconLink); 102 | return resultMap; 103 | } 104 | 105 | @Override 106 | public Map getPageList(Long groupId, Integer current, Integer size) { 107 | Long accountId = LoginInterceptor.threadLocal.get().getAccountId(); 108 | Page page = urlTobStore.getPage(accountId, groupId, current, size); 109 | List list = LinkUrlAdapter.buildLinkUrlTobVOList(page.getRecords()); 110 | Map resultMap = new HashMap<>(); 111 | resultMap.put("list", list); 112 | resultMap.put("total", page.getTotal()); 113 | return resultMap; 114 | } 115 | 116 | @Override 117 | public LinkUrlTobVO detail(Long id) { 118 | Long accountId = LoginInterceptor.threadLocal.get().getAccountId(); 119 | LinkUrlTob urlTob = urlTobStore.getLinkDetail(id, accountId); 120 | if (ObjectUtils.isEmpty(urlTob)) { 121 | return null; 122 | } 123 | return LinkUrlAdapter.buildLinkUrlTobVO(urlTob); 124 | } 125 | 126 | @Override 127 | public void deleteUrl(Long id) { 128 | 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/service/impl/LinkUrlTocServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service.impl; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; 5 | import com.minilink.adapter.KafkaMsgAdapter; 6 | import com.minilink.constant.KafkaConstant; 7 | import com.minilink.constant.RegexConstant; 8 | import com.minilink.pojo.entity.VisitShortLinkMsg; 9 | import com.minilink.pojo.po.LinkUrlToc; 10 | import com.minilink.service.LinkUrlTocService; 11 | import com.minilink.store.LinkUrlTocStore; 12 | import com.minilink.util.HttpServletUtil; 13 | import jakarta.servlet.http.HttpServletResponse; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.http.HttpStatus; 16 | import org.springframework.kafka.core.KafkaTemplate; 17 | import org.springframework.stereotype.Service; 18 | 19 | import java.time.LocalDateTime; 20 | 21 | /** 22 | *

23 | * 服务实现类 24 | *

25 | * 26 | * @author 徐志斌 27 | * @since 2024-12-14 28 | */ 29 | @Service 30 | public class LinkUrlTocServiceImpl implements LinkUrlTocService { 31 | @Autowired 32 | private LinkUrlTocStore urlTocStore; 33 | @Autowired 34 | private KafkaTemplate kafkaTemplate; 35 | 36 | @Override 37 | public void redirect(String shortLinkCode) { 38 | HttpServletResponse response = HttpServletUtil.getResponse(); 39 | if (!shortLinkCode.matches(RegexConstant.REGEX_SHORT_LINK_FORMAT)) { 40 | response.setStatus(HttpStatus.NOT_FOUND.value()); 41 | return; 42 | } 43 | LinkUrlToc linkUrlPO = urlTocStore.getByShortLinkCode(shortLinkCode); 44 | if (ObjectUtils.isEmpty(linkUrlPO)) { 45 | response.setStatus(HttpStatus.NOT_FOUND.value()); 46 | return; 47 | } 48 | if (ObjectUtils.isNotEmpty(linkUrlPO.getExpiredTime()) && linkUrlPO.getExpiredTime().isBefore(LocalDateTime.now())) { 49 | response.setStatus(HttpStatus.NOT_FOUND.value()); 50 | return; 51 | } 52 | 53 | VisitShortLinkMsg visitShortLinkMsg = KafkaMsgAdapter.buildVisitShortLinkMsg( 54 | linkUrlPO.getAccountId(), 55 | HttpServletUtil.getRequest().getHeader("User-Agent"), 56 | linkUrlPO.getShortLinkCode() 57 | ); 58 | kafkaTemplate.send(KafkaConstant.ODS_CLICK_LINK_TOPIC, JSONUtil.toJsonStr(visitShortLinkMsg)); 59 | 60 | response.setHeader("Location", linkUrlPO.getLongLink()); 61 | response.setStatus(HttpStatus.FOUND.value()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/sharding/ShardingConfigFactory.java: -------------------------------------------------------------------------------- 1 | package com.minilink.sharding; 2 | 3 | import com.minilink.enums.BusinessCodeEnum; 4 | import com.minilink.exception.BusinessException; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Random; 9 | 10 | /** 11 | * @Author: 徐志斌 12 | * @CreateTime: 2024-12-13 10:44 13 | * @Description: 库表位-配置工厂 14 | * @Version: 1.0 15 | */ 16 | public class ShardingConfigFactory { 17 | public static final List databaseList = new ArrayList<>(); 18 | public static final List tableList = new ArrayList<>(); 19 | private static final Random random = new Random(); 20 | 21 | static { 22 | databaseList.add(new ShardingElement("0", 1)); 23 | databaseList.add(new ShardingElement("1", 1)); 24 | databaseList.add(new ShardingElement("2", 1)); 25 | } 26 | 27 | static { 28 | tableList.add(new ShardingElement("0", 1)); 29 | tableList.add(new ShardingElement("1", 1)); 30 | } 31 | 32 | public static String getCode(List list) { 33 | int totalWeight = 0; 34 | for (ShardingElement element : list) { 35 | totalWeight += element.getWeight(); 36 | } 37 | int currentWeight = 0; 38 | int randomCode = random.nextInt(totalWeight); 39 | for (ShardingElement element : list) { 40 | currentWeight += element.getWeight(); 41 | if (randomCode < currentWeight) { 42 | return element.getName(); 43 | } 44 | } 45 | throw new BusinessException(BusinessCodeEnum.FAIL); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/sharding/ShardingElement.java: -------------------------------------------------------------------------------- 1 | package com.minilink.sharding; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | /** 8 | * @Author 徐志斌 9 | * @Date: 2024/12/15 18:15 10 | * @Version 1.0 11 | * @Description: 库、表位元素:用于判断分库分表权重 12 | */ 13 | @Getter 14 | @Setter 15 | @AllArgsConstructor 16 | public class ShardingElement { 17 | /** 18 | * 库号 | 表号 19 | */ 20 | private String name; 21 | 22 | /** 23 | * 权重 24 | */ 25 | private Integer weight; 26 | } 27 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/sharding/algorithm/LinkTocDatabaseShardingAlgorithm.java: -------------------------------------------------------------------------------- 1 | package com.minilink.sharding.algorithm; 2 | 3 | import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue; 4 | import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue; 5 | import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * @Author 徐志斌 11 | * @Date: 2024/12/15 16:26 12 | * @Version 1.0 13 | * @Description: ToC短链接-分库算法 14 | */ 15 | public class LinkTocDatabaseShardingAlgorithm implements StandardShardingAlgorithm { 16 | 17 | @Override 18 | public String doSharding(Collection collection, PreciseShardingValue preciseShardingValue) { 19 | String shortLinkCode = preciseShardingValue.getValue(); 20 | String databaseName = "ds"; 21 | String databaseCode = shortLinkCode.split("-")[0]; 22 | return databaseName + databaseCode; 23 | } 24 | 25 | @Override 26 | public Collection doSharding(Collection collection, RangeShardingValue rangeShardingValue) { 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/sharding/algorithm/LinkTocTableShardingAlgorithm.java: -------------------------------------------------------------------------------- 1 | package com.minilink.sharding.algorithm; 2 | 3 | import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue; 4 | import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue; 5 | import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * @Author 徐志斌 11 | * @Date: 2024/12/15 16:26 12 | * @Version 1.0 13 | * @Description: ToC短链接-分表算法 14 | */ 15 | public class LinkTocTableShardingAlgorithm implements StandardShardingAlgorithm { 16 | 17 | @Override 18 | public String doSharding(Collection collection, PreciseShardingValue preciseShardingValue) { 19 | String shortLinkCode = preciseShardingValue.getValue(); 20 | String tableName = preciseShardingValue.getLogicTableName(); 21 | String tableCode = shortLinkCode.split("-")[1]; 22 | return tableName + "_" + tableCode; 23 | } 24 | 25 | @Override 26 | public Collection doSharding(Collection collection, RangeShardingValue rangeShardingValue) { 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/store/LinkGroupStore.java: -------------------------------------------------------------------------------- 1 | package com.minilink.store; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.minilink.pojo.po.LinkGroup; 5 | 6 | /** 7 | *

8 | * 服务类 9 | *

10 | * 11 | * @author 徐志斌 12 | * @since 2024-12-12 13 | */ 14 | public interface LinkGroupStore extends IService { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/store/LinkUrlTobStore.java: -------------------------------------------------------------------------------- 1 | package com.minilink.store; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.minilink.pojo.po.LinkUrlTob; 6 | 7 | /** 8 | *

9 | * 服务类 10 | *

11 | * 12 | * @author 徐志斌 13 | * @since 2024-12-09 14 | */ 15 | public interface LinkUrlTobStore extends IService { 16 | /** 17 | * 保存链接记录 18 | * 19 | * @param linkUrl 链接信息 20 | * @return 执行结果 21 | */ 22 | Boolean saveLink(LinkUrlTob linkUrl); 23 | 24 | /** 25 | * 分页查询短链接列表 26 | * 27 | * @param accountId 账号 28 | * @param groupId 分组id 29 | * @param current 当前页 30 | * @param size 每页条数 31 | * @return 分页结果 32 | */ 33 | Page getPage(Long accountId, Long groupId, Integer current, Integer size); 34 | 35 | /** 36 | * 根据 account_id 和 id 查询链接信息 37 | * 38 | * @param id 主键id 39 | * @param accountId 账号 40 | * @return 链接详情 41 | */ 42 | LinkUrlTob getLinkDetail(Long id, Long accountId); 43 | 44 | /** 45 | * 根据 id 删除短链接 46 | * 47 | * @param id 链接id 48 | */ 49 | Boolean deleteUrlById(Long id); 50 | } 51 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/store/LinkUrlTocStore.java: -------------------------------------------------------------------------------- 1 | package com.minilink.store; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.minilink.pojo.po.LinkUrlToc; 5 | 6 | /** 7 | *

8 | * 服务类 9 | *

10 | * 11 | * @author 徐志斌 12 | * @since 2024-12-14 13 | */ 14 | public interface LinkUrlTocStore extends IService { 15 | /** 16 | * 保存链接记录 17 | * 18 | * @param linkUrl 链接信息 19 | * @return 执行结果 20 | */ 21 | Boolean saveLink(LinkUrlToc linkUrl); 22 | 23 | /** 24 | * 根据短链接查询记录 25 | * 26 | * @param shortLinkCode 短链接码 27 | * @return 链接记录 28 | */ 29 | LinkUrlToc getByShortLinkCode(String shortLinkCode); 30 | } 31 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/store/impl/LinkGroupStoreImpl.java: -------------------------------------------------------------------------------- 1 | package com.minilink.store.impl; 2 | 3 | import com.minilink.service.LinkGroupService; 4 | import org.springframework.stereotype.Service; 5 | 6 | /** 7 | *

8 | * 服务实现类 9 | *

10 | * 11 | * @author 徐志斌 12 | * @since 2024-12-12 13 | */ 14 | @Service 15 | public class LinkGroupStoreImpl implements LinkGroupService { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/store/impl/LinkUrlTobStoreImpl.java: -------------------------------------------------------------------------------- 1 | package com.minilink.store.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import com.minilink.constant.RedisConstant; 7 | import com.minilink.mapper.LinkUrlTobMapper; 8 | import com.minilink.pojo.po.LinkUrlTob; 9 | import com.minilink.store.LinkUrlTobStore; 10 | import org.springframework.cache.annotation.Cacheable; 11 | import org.springframework.stereotype.Service; 12 | 13 | /** 14 | *

15 | * 服务实现类 16 | *

17 | * 18 | * @author 徐志斌 19 | * @since 2024-12-09 20 | */ 21 | @Service 22 | public class LinkUrlTobStoreImpl extends ServiceImpl implements LinkUrlTobStore { 23 | @Override 24 | public Boolean saveLink(LinkUrlTob linkUrl) { 25 | return this.save(linkUrl); 26 | } 27 | 28 | @Override 29 | @Cacheable(value = RedisConstant.CACHE_LINK_URL_TOB, key = "#p0 + #p1 + #p2 + #p3", unless = "#result.records.isEmpty()") 30 | public Page getPage(Long accountId, Long groupId, Integer current, Integer size) { 31 | LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); 32 | wrapper.eq(LinkUrlTob::getAccountId, accountId); 33 | wrapper.eq(LinkUrlTob::getGroupId, groupId); 34 | return this.page(new Page<>(current, size), wrapper); 35 | } 36 | 37 | @Override 38 | @Cacheable(value = RedisConstant.CACHE_LINK_URL_TOB, key = "#p0 + #p1", unless = "#result == null") 39 | public LinkUrlTob getLinkDetail(Long id, Long accountId) { 40 | return this.lambdaQuery() 41 | .eq(LinkUrlTob::getId, id) 42 | .eq(LinkUrlTob::getAccountId, accountId) 43 | .one(); 44 | } 45 | 46 | @Override 47 | public Boolean deleteUrlById(Long id) { 48 | return this.removeById(id); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/store/impl/LinkUrlTocStoreImpl.java: -------------------------------------------------------------------------------- 1 | package com.minilink.store.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.minilink.mapper.LinkUrlTocMapper; 5 | import com.minilink.pojo.po.LinkUrlToc; 6 | import com.minilink.store.LinkUrlTocStore; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | *

11 | * 服务实现类 12 | *

13 | * 14 | * @author 徐志斌 15 | * @since 2024-12-09 16 | */ 17 | @Service 18 | public class LinkUrlTocStoreImpl extends ServiceImpl implements LinkUrlTocStore { 19 | @Override 20 | public Boolean saveLink(LinkUrlToc linkUrl) { 21 | return this.save(linkUrl); 22 | } 23 | 24 | @Override 25 | public LinkUrlToc getByShortLinkCode(String shortLinkCode) { 26 | return this.lambdaQuery() 27 | .eq(LinkUrlToc::getShortLinkCode, shortLinkCode) 28 | .one(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mini-link-core/src/main/java/com/minilink/util/LinkUrlUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | import com.google.common.hash.Hashing; 4 | import com.minilink.sharding.ShardingConfigFactory; 5 | 6 | /** 7 | * @Author: 徐志斌 8 | * @CreateTime: 2024-12-12 13:12 9 | * @Description: 链接工具类 10 | * @Version: 1.0 11 | */ 12 | public class LinkUrlUtil { 13 | private static final String CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 14 | 15 | private static long murmurHash32(String str) { 16 | return Hashing.murmur3_32_fixed().hashUnencodedChars(str).padToLong(); 17 | } 18 | 19 | private static String encodeToBase62(long num) { 20 | StringBuffer sb = new StringBuffer(); 21 | do { 22 | int i = (int) (num % CHARS.length()); 23 | sb.append(CHARS.charAt(i)); 24 | num = num / CHARS.length(); 25 | } while (num > 0); 26 | return sb.reverse().toString(); 27 | } 28 | 29 | /** 30 | * 生成短链接:Murmurhash算法 + Base62编码 31 | * 格式:库号-表号-短链接 32 | * 例如:0-2-4s3sQA 33 | */ 34 | public static String generate(String longLink) { 35 | long murmurHash32 = murmurHash32(longLink); 36 | StringBuffer sb = new StringBuffer(); 37 | sb.append(ShardingConfigFactory.getCode(ShardingConfigFactory.databaseList)); 38 | sb.append("-"); 39 | sb.append(ShardingConfigFactory.getCode(ShardingConfigFactory.tableList)); 40 | sb.append("-"); 41 | sb.append(encodeToBase62(murmurHash32)); 42 | return sb.toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mini-link-core/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | mini-link: 2 | domain: localhost:8080/ 3 | group-id: 1 4 | spring: 5 | datasource: 6 | driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver 7 | url: jdbc:shardingsphere:classpath:sharding-dev.yaml 8 | data: 9 | redis: 10 | host: localhost 11 | port: 6379 12 | database: 0 13 | password: Pxy123456 14 | timeout: 3000 15 | connectTimeout: 5000 16 | lettuce: 17 | pool: 18 | min-idle: 100 19 | max-idle: 100 20 | max-active: 100 21 | max-wait: 20000 -------------------------------------------------------------------------------- /mini-link-core/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: dev 4 | application: 5 | name: mini-link-core 6 | server: 7 | port: 8002 8 | -------------------------------------------------------------------------------- /mini-link-core/src/main/resources/deploy/deployment-core.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mini-link-core-deployment 5 | namespace: mini-link-namespace 6 | labels: 7 | app: mini-link-core-label 8 | spec: 9 | replicas: 9 10 | selector: 11 | matchLabels: 12 | app: mini-link-core-label 13 | template: 14 | metadata: 15 | labels: 16 | app: mini-link-core-label 17 | spec: 18 | containers: 19 | - name: backend 20 | image: 镜像仓库IP:端口/minilink/mini-link-core:1.0 21 | imagePullPolicy: Always 22 | ports: 23 | - containerPort: 8002 24 | resources: 25 | requests: 26 | memory: 300Mi 27 | cpu: 200m 28 | limits: 29 | memory: 500Mi 30 | cpu: 400m 31 | readinessProbe: 32 | tcpSocket: 33 | port: 8002 34 | initialDelaySeconds: 60 35 | periodSeconds: 10 36 | livenessProbe: 37 | tcpSocket: 38 | port: 8002 39 | initialDelaySeconds: 60 40 | periodSeconds: 10 -------------------------------------------------------------------------------- /mini-link-core/src/main/resources/deploy/service-core.yml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: backend-service 5 | namespace: mini-link-namespace 6 | labels: 7 | app: mini-link-core-label 8 | spec: 9 | selector: 10 | app: mini-link-core-label 11 | type: ClusterIP 12 | ports: 13 | - protocol: TCP 14 | port: 8002 15 | targetPort: 8002 16 | -------------------------------------------------------------------------------- /mini-link-core/src/main/resources/deploy/sql/mini_link_core.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : localhost 5 | Source Server Type : MySQL 6 | Source Server Version : 101102 (10.11.2-MariaDB) 7 | Source Host : localhost:3306 8 | Source Schema : mini_link_core_0 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 101102 (10.11.2-MariaDB) 12 | File Encoding : 65001 13 | 14 | Date: 16/12/2024 13:27:08 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for link_url_tob_0 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `link_url_tob_0`; 24 | CREATE TABLE `link_url_tob_0` ( 25 | `id` bigint NOT NULL COMMENT '主键id', 26 | `group_id` bigint NULL DEFAULT NULL COMMENT '链接分组id', 27 | `account_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '账号', 28 | `title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '链接标题', 29 | `icon` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '链接icon', 30 | `domain` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '域名', 31 | `short_link_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '短链接码(1-0-cXcLm)', 32 | `short_link` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '短链接', 33 | `long_link` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '长链接', 34 | `qr_code` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '短链接二维码', 35 | `expired_time` datetime NULL DEFAULT NULL COMMENT '到期时间', 36 | `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 37 | `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间', 38 | `deleted` tinyint(1) NULL DEFAULT NULL COMMENT '删除标识', 39 | PRIMARY KEY (`id`) USING BTREE 40 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = DYNAMIC; 41 | 42 | -- ---------------------------- 43 | -- Table structure for link_url_tob_1 44 | -- ---------------------------- 45 | DROP TABLE IF EXISTS `link_url_tob_1`; 46 | CREATE TABLE `link_url_tob_1` ( 47 | `id` bigint NOT NULL COMMENT '主键id', 48 | `group_id` bigint NULL DEFAULT NULL COMMENT '链接分组id', 49 | `account_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '账号', 50 | `title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '链接标题', 51 | `icon` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '链接icon', 52 | `domain` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '域名', 53 | `short_link_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '短链接码(1-0-cXcLm)', 54 | `short_link` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '短链接', 55 | `long_link` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '长链接', 56 | `qr_code` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '短链接二维码', 57 | `expired_time` datetime NULL DEFAULT NULL COMMENT '到期时间', 58 | `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 59 | `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间', 60 | `deleted` tinyint(1) NULL DEFAULT NULL COMMENT '删除标识', 61 | PRIMARY KEY (`id`) USING BTREE 62 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = DYNAMIC; 63 | 64 | -- ---------------------------- 65 | -- Table structure for link_url_toc_0 66 | -- ---------------------------- 67 | DROP TABLE IF EXISTS `link_url_toc_0`; 68 | CREATE TABLE `link_url_toc_0` ( 69 | `id` bigint NOT NULL COMMENT '主键id', 70 | `short_link_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '短链接码(1-0-cXcLm)', 71 | `short_link` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '短链接', 72 | `long_link` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '长链接', 73 | `expired_time` datetime NULL DEFAULT NULL COMMENT '到期时间', 74 | `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 75 | `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间', 76 | `deleted` tinyint(1) NULL DEFAULT NULL COMMENT '删除标识', 77 | PRIMARY KEY (`id`) USING BTREE 78 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = DYNAMIC; 79 | 80 | -- ---------------------------- 81 | -- Table structure for link_url_toc_1 82 | -- ---------------------------- 83 | DROP TABLE IF EXISTS `link_url_toc_1`; 84 | CREATE TABLE `link_url_toc_1` ( 85 | `id` bigint NOT NULL COMMENT '主键id', 86 | `short_link_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '短链接码(1-0-cXcLm)', 87 | `short_link` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '短链接', 88 | `long_link` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '长链接', 89 | `expired_time` datetime NULL DEFAULT NULL COMMENT '到期时间', 90 | `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 91 | `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间', 92 | `deleted` tinyint(1) NULL DEFAULT NULL COMMENT '删除标识', 93 | PRIMARY KEY (`id`) USING BTREE 94 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = DYNAMIC; 95 | 96 | -- ---------------------------- 97 | -- Table structure for mini_link_group 98 | -- ---------------------------- 99 | DROP TABLE IF EXISTS `mini_link_group`; 100 | CREATE TABLE `mini_link_group` ( 101 | `id` bigint NOT NULL COMMENT '主键', 102 | `account_id` bigint NULL DEFAULT NULL COMMENT '账号', 103 | `group_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '分组名称', 104 | `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 105 | `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间', 106 | `deleted` tinyint(1) NULL DEFAULT NULL COMMENT '删除标识', 107 | PRIMARY KEY (`id`) USING BTREE 108 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = DYNAMIC; 109 | 110 | SET FOREIGN_KEY_CHECKS = 1; 111 | -------------------------------------------------------------------------------- /mini-link-core/src/main/resources/mapper/LinkGroupMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | id, account_id, group_name, create_time, update_time, deleted 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /mini-link-core/src/main/resources/mapper/LinkUrlMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | id, group_id, account_id, title, short_link, long_link, expired_time, create_time, update_time, deleted 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /mini-link-core/src/main/resources/mapper/LinkUrlTocMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | id, short_link, long_link, expired_time 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /mini-link-core/src/main/resources/sharding-dev.yaml: -------------------------------------------------------------------------------- 1 | dataSources: 2 | ds0: 3 | dataSourceClassName: com.zaxxer.hikari.HikariDataSource 4 | driverClassName: com.mysql.cj.jdbc.Driver 5 | jdbcUrl: jdbc:mysql://127.0.0.1:3306/mini_link_core_0?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai 6 | username: root 7 | password: Pxy161122 8 | ds1: 9 | dataSourceClassName: com.zaxxer.hikari.HikariDataSource 10 | driverClassName: com.mysql.cj.jdbc.Driver 11 | jdbcUrl: jdbc:mysql://127.0.0.1:3306/mini_link_core_1?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai 12 | username: root 13 | password: Pxy161122 14 | ds2: 15 | dataSourceClassName: com.zaxxer.hikari.HikariDataSource 16 | driverClassName: com.mysql.cj.jdbc.Driver 17 | jdbcUrl: jdbc:mysql://127.0.0.1:3306/mini_link_core_2?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai 18 | username: root 19 | password: Pxy161122 20 | 21 | rules: 22 | - !SHARDING 23 | tables: 24 | # 短链接 ToB 25 | link_url_tob: 26 | actualDataNodes: ds${0..2}.link_url_tob_${0..1} 27 | databaseStrategy: 28 | standard: 29 | shardingColumn: account_id 30 | shardingAlgorithmName: link-url-tob-datasource 31 | tableStrategy: 32 | standard: 33 | shardingColumn: account_id 34 | shardingAlgorithmName: link-url-tob-table 35 | keyGenerateStrategy: 36 | column: id 37 | keyGeneratorName: snowflake 38 | # 短链接 ToC 39 | link_url_toc: 40 | actualDataNodes: ds${0..2}.link_url_toc_${0..1} 41 | databaseStrategy: 42 | standard: 43 | shardingColumn: short_link_code 44 | shardingAlgorithmName: link-url-toc-datasource 45 | tableStrategy: 46 | standard: 47 | shardingColumn: short_link_code 48 | shardingAlgorithmName: link-url-toc-table 49 | keyGenerateStrategy: 50 | column: id 51 | keyGeneratorName: snowflake 52 | shardingAlgorithms: 53 | # 短链接 ToB 54 | link-url-tob-datasource: 55 | type: INLINE 56 | props: 57 | algorithm-expression: ds${account_id % 3} 58 | link-url-tob-table: 59 | type: INLINE 60 | props: 61 | algorithm-expression: link_url_tob_${account_id % 2} 62 | # 短链接 ToC 63 | link-url-toc-datasource: 64 | type: CLASS_BASED 65 | props: 66 | strategy: STANDARD 67 | algorithmClassName: com.minilink.sharding.algorithm.LinkTocDatabaseShardingAlgorithm 68 | link-url-toc-table: 69 | type: CLASS_BASED 70 | props: 71 | strategy: STANDARD 72 | algorithmClassName: com.minilink.sharding.algorithm.LinkTocTableShardingAlgorithm 73 | keyGenerators: 74 | snowflake: 75 | type: SNOWFLAKE 76 | props: 77 | worker: 78 | id: ${workerId} 79 | props: 80 | sql-show: true 81 | -------------------------------------------------------------------------------- /mini-link-data/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | com.minilink 6 | mini-link 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | 4.0.0 11 | com.minilink 12 | mini-link-data 13 | 0.0.1-SNAPSHOT 14 | mini-link-data 15 | 16 | 17 | 18 | com.minilink 19 | mini-link-common 20 | 0.0.1-SNAPSHOT 21 | 22 | 23 | ru.yandex.clickhouse 24 | clickhouse-jdbc 25 | 0.1.55 26 | 27 | 28 | 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-maven-plugin 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /mini-link-data/src/main/java/com/minilink/MiniLinkDataApplication.java: -------------------------------------------------------------------------------- 1 | package com.minilink; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MiniLinkDataApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MiniLinkDataApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mini-link-data/src/main/java/com/minilink/controller/ShortLinkStatisticsController.java: -------------------------------------------------------------------------------- 1 | package com.minilink.controller; 2 | 3 | /** 4 | * @Author 徐志斌 5 | * @Date: 2025/1/1 17:39 6 | * @Version 1.0 7 | * @Description: 短链接相关-数据统计 8 | */ 9 | public class ShortLinkStatisticsController { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /mini-link-data/src/main/java/com/minilink/mapper/ShortLinkStatisticsMapper.java: -------------------------------------------------------------------------------- 1 | package com.minilink.mapper; 2 | 3 | public interface ShortLinkStatisticsMapper { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /mini-link-data/src/main/java/com/minilink/service/ShortLinkStatisticsService.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service; 2 | 3 | public interface ShortLinkStatisticsService { 4 | } 5 | -------------------------------------------------------------------------------- /mini-link-data/src/main/java/com/minilink/service/impl/ShortLinkStatisticsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service.impl; 2 | 3 | import com.minilink.service.ShortLinkStatisticsService; 4 | 5 | /** 6 | * @Author 徐志斌 7 | * @Date: 2025/1/1 17:41 8 | * @Version 1.0 9 | * @Description: ShortLinkStatisticsServiceImpl 10 | */ 11 | public class ShortLinkStatisticsServiceImpl implements ShortLinkStatisticsService { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mini-link-data/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | driver-class-name: ru.yandex.clickhouse.ClickHouseDriver 4 | url: jdbc:clickhouse://localhost:8123/default 5 | username: root 6 | password: test123456 7 | data: 8 | redis: 9 | host: localhost 10 | port: 6379 11 | database: 0 12 | password: test123456 13 | timeout: 3000 14 | connectTimeout: 5000 15 | lettuce: 16 | pool: 17 | min-idle: 100 18 | max-idle: 100 19 | max-active: 100 20 | max-wait: 20000 -------------------------------------------------------------------------------- /mini-link-data/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: mini-link-data 4 | profiles: 5 | active: dev 6 | server: 7 | port: 8003 8 | servlet: 9 | context-path: /data 10 | -------------------------------------------------------------------------------- /mini-link-data/src/main/resources/deploy/clickhouse.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE default.visit_stats 2 | ( 3 | `account_id` UInt64, 4 | `short_link_code` String, 5 | `ip` String, 6 | `referer` String, 7 | `province` String, 8 | `city` String, 9 | `browser_type` String, 10 | `device_type` String, 11 | `os_type` String, 12 | `visitor_state` UInt64, 13 | `pv` UInt64, 14 | `uv` UInt64, 15 | `start_time` DateTime, 16 | `end_time` DateTime, 17 | `create_time` UInt64 18 | ) ENGINE = ReplacingMergeTree(create_time) 19 | PARTITION BY toYYYYMMDD(start_time) 20 | ORDER BY ( 21 | start_time, 22 | end_time, 23 | short_link_code, 24 | province, 25 | city, 26 | referer, 27 | visitor_state, 28 | ip, 29 | browser_type, 30 | os_type, 31 | device_type); -------------------------------------------------------------------------------- /mini-link-data/src/main/resources/deploy/deployment-data.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mini-link-data-deployment 5 | namespace: mini-link-namespace 6 | labels: 7 | app: mini-link-data-label 8 | spec: 9 | replicas: 5 10 | selector: 11 | matchLabels: 12 | app: mini-link-data-label 13 | template: 14 | metadata: 15 | labels: 16 | app: mini-link-data-label 17 | spec: 18 | containers: 19 | - name: mini-link-gateway 20 | image: 镜像仓库IP:端口/minilink/mini-link-data:1.0 21 | imagePullPolicy: Always 22 | ports: 23 | - containerPort: 8003 24 | resources: 25 | requests: 26 | memory: 300Mi 27 | cpu: 200m 28 | limits: 29 | memory: 500Mi 30 | cpu: 400m 31 | readinessProbe: 32 | tcpSocket: 33 | port: 8003 34 | initialDelaySeconds: 60 35 | periodSeconds: 10 36 | livenessProbe: 37 | tcpSocket: 38 | port: 8003 39 | initialDelaySeconds: 60 40 | periodSeconds: 10 -------------------------------------------------------------------------------- /mini-link-data/src/main/resources/deploy/service-data.yml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: mini-link-data-service 5 | namespace: mini-link-namespace 6 | labels: 7 | app: mini-link-data-label 8 | spec: 9 | selector: 10 | app: mini-link-data-label 11 | type: ClusterIP 12 | ports: 13 | - protocol: TCP 14 | port: 8003 15 | targetPort: 8003 16 | -------------------------------------------------------------------------------- /mini-link-flink/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | com.minilink 6 | mini-link 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | 4.0.0 11 | com.minilink 12 | mini-link-flink 13 | 0.0.1-SNAPSHOT 14 | mini-link-flink 15 | 16 | 17 | 1.13.1 18 | 2.12 19 | 20 | 21 | 22 | 23 | org.apache.flink 24 | flink-clients_${scala.version} 25 | ${flink.version} 26 | 27 | 28 | org.apache.flink 29 | flink-connector-kafka_${scala.version} 30 | ${flink.version} 31 | 32 | 33 | org.apache.flink 34 | flink-connector-jdbc_${scala.version} 35 | ${flink.version} 36 | 37 | 38 | 39 | cn.hutool 40 | hutool-all 41 | 42 | 43 | org.projectlombok 44 | lombok 45 | 46 | 47 | com.squareup.okhttp3 48 | okhttp 49 | 50 | 51 | eu.bitwalker 52 | UserAgentUtils 53 | 1.21 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-maven-plugin 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/app/dwd/DwdClickLinkApp.java: -------------------------------------------------------------------------------- 1 | package com.minilink.app.dwd; 2 | 3 | import cn.hutool.json.JSONObject; 4 | import cn.hutool.json.JSONUtil; 5 | import com.minilink.app.func.VisitorStateRichMapFunction; 6 | import com.minilink.constant.KafkaConstant; 7 | import com.minilink.util.FlinkKafkaUtil; 8 | import org.apache.flink.api.common.functions.MapFunction; 9 | import org.apache.flink.api.java.functions.KeySelector; 10 | import org.apache.flink.streaming.api.datastream.DataStreamSource; 11 | import org.apache.flink.streaming.api.datastream.KeyedStream; 12 | import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; 13 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 14 | import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; 15 | import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer; 16 | 17 | /** 18 | * @Author: 徐志斌 19 | * @CreateTime: 2024-12-23 15:30 20 | * @Description: DWD-访问短链接 21 | * @Version: 1.0 22 | */ 23 | public class DwdClickLinkApp { 24 | public static final String SOURCE_TOPIC = KafkaConstant.ODS_CLICK_LINK_TOPIC; 25 | public static final String SINK_TOPIC = KafkaConstant.DWD_CLICK_LINK_TOPIC; 26 | public static final String DWD_CLICK_LINK_GROUP = KafkaConstant.DWD_CLICK_LINK_GROUP; 27 | 28 | public static void main(String[] args) throws Exception { 29 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 30 | FlinkKafkaConsumer kafkaConsumer = FlinkKafkaUtil.getKafkaConsumer(SOURCE_TOPIC, DWD_CLICK_LINK_GROUP); 31 | DataStreamSource jsonStrDS = env.addSource(kafkaConsumer); 32 | jsonStrDS.print(">>>>>>>>DWD-jsonStrDS"); 33 | 34 | SingleOutputStreamOperator jsonObjDS = jsonStrDS.map( 35 | new MapFunction() { 36 | @Override 37 | public JSONObject map(String msg) { 38 | return JSONUtil.toBean(msg, JSONObject.class); 39 | } 40 | } 41 | ); 42 | 43 | KeyedStream keyedStream = jsonObjDS.keyBy( 44 | new KeySelector() { 45 | @Override 46 | public String getKey(JSONObject jsonStr) { 47 | return jsonStr.getStr("userAgent"); 48 | } 49 | } 50 | ); 51 | 52 | SingleOutputStreamOperator visitorStateDS = keyedStream.map(new VisitorStateRichMapFunction()); 53 | FlinkKafkaProducer kafkaProducer = FlinkKafkaUtil.getKafkaProducer(SINK_TOPIC); 54 | visitorStateDS.addSink(kafkaProducer); 55 | visitorStateDS.print(">>>>>>>>DWD-visitorStateDS"); 56 | env.execute(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/app/dwm/DwmLinkWideLogApp.java: -------------------------------------------------------------------------------- 1 | package com.minilink.app.dwm; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.minilink.app.func.DeviceMapFunction; 5 | import com.minilink.app.func.LocationMapFunction; 6 | import com.minilink.constant.KafkaConstant; 7 | import com.minilink.pojo.VisitShortLinkLog; 8 | import com.minilink.util.FlinkKafkaUtil; 9 | import org.apache.flink.streaming.api.datastream.DataStreamSource; 10 | import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; 11 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 12 | import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; 13 | import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer; 14 | 15 | /** 16 | * @Author: 徐志斌 17 | * @CreateTime: 2024-12-26 11:20 18 | * @Description: DWM-设备、地区信息补充 19 | * @Version: 1.0 20 | */ 21 | public class DwmLinkWideLogApp { 22 | public static final String SOURCE_TOPIC = KafkaConstant.DWD_CLICK_LINK_TOPIC; 23 | public static final String SINK_TOPIC = KafkaConstant.DWM_WIDE_LOG_TOPIC; 24 | public static final String GROUP = KafkaConstant.DWM_WIDE_LOG_GROUP; 25 | 26 | public static void main(String[] args) throws Exception { 27 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 28 | FlinkKafkaConsumer kafkaConsumer = FlinkKafkaUtil.getKafkaConsumer(SOURCE_TOPIC, GROUP); 29 | DataStreamSource jsonStrDS = env.addSource(kafkaConsumer); 30 | 31 | SingleOutputStreamOperator addDeviceDS = jsonStrDS.map(new DeviceMapFunction()); 32 | addDeviceDS.print(">>>>>>>>DWM-addDeviceDS"); 33 | 34 | SingleOutputStreamOperator addRegionDS = addDeviceDS.map(new LocationMapFunction()); 35 | addRegionDS.print(">>>>>>>>DWM-addRegionDS"); 36 | 37 | SingleOutputStreamOperator jsonStr = addRegionDS.map(JSONUtil::toJsonStr); 38 | FlinkKafkaProducer kafkaProducer = FlinkKafkaUtil.getKafkaProducer(SINK_TOPIC); 39 | jsonStr.addSink(kafkaProducer); 40 | env.execute(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/app/dwm/DwmUniqueVisitorApp.java: -------------------------------------------------------------------------------- 1 | package com.minilink.app.dwm; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.minilink.app.func.VisitorUniqueRichFilterFunction; 5 | import com.minilink.constant.KafkaConstant; 6 | import com.minilink.pojo.VisitShortLinkLog; 7 | import com.minilink.util.FlinkKafkaUtil; 8 | import org.apache.flink.api.common.functions.MapFunction; 9 | import org.apache.flink.api.java.functions.KeySelector; 10 | import org.apache.flink.streaming.api.datastream.DataStreamSource; 11 | import org.apache.flink.streaming.api.datastream.KeyedStream; 12 | import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; 13 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 14 | import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; 15 | import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer; 16 | 17 | /** 18 | * @Author: 徐志斌 19 | * @CreateTime: 2024-12-30 13:40 20 | * @Description: DWM-访客去重 21 | * @Version: 1.0 22 | */ 23 | public class DwmUniqueVisitorApp { 24 | public static final String SOURCE_TOPIC = KafkaConstant.DWM_WIDE_LOG_TOPIC; 25 | public static final String SINK_TOPIC = KafkaConstant.DWM_UNIQUE_VISITOR_TOPIC; 26 | public static final String GROUP = KafkaConstant.DWM_UNIQUE_VISITOR_GROUP; 27 | 28 | public static void main(String[] args) throws Exception { 29 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 30 | FlinkKafkaConsumer kafkaConsumer = FlinkKafkaUtil.getKafkaConsumer(SOURCE_TOPIC, GROUP); 31 | DataStreamSource jsonStrDS = env.addSource(kafkaConsumer); 32 | jsonStrDS.print(">>>>>>>>DWM-jsonStrDS"); 33 | 34 | SingleOutputStreamOperator wideLogDS = jsonStrDS.map( 35 | new MapFunction() { 36 | @Override 37 | public VisitShortLinkLog map(String msg) throws Exception { 38 | return JSONUtil.toBean(msg, VisitShortLinkLog.class); 39 | } 40 | } 41 | ); 42 | 43 | KeyedStream groupDS = wideLogDS.keyBy( 44 | new KeySelector() { 45 | @Override 46 | public String getKey(VisitShortLinkLog wideLog) { 47 | return wideLog.getUserAgent(); 48 | } 49 | } 50 | ); 51 | 52 | SingleOutputStreamOperator uniqueVisitorDS = groupDS.filter(new VisitorUniqueRichFilterFunction()); 53 | uniqueVisitorDS.print(">>>>>>>>DWM-uniqueVisitorDS"); 54 | 55 | SingleOutputStreamOperator jsonStr = uniqueVisitorDS.map(JSONUtil::toJsonStr); 56 | FlinkKafkaProducer kafkaProducer = FlinkKafkaUtil.getKafkaProducer(SINK_TOPIC); 57 | jsonStr.addSink(kafkaProducer); 58 | env.execute(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/app/dws/DwsClickLinkApp.java: -------------------------------------------------------------------------------- 1 | package com.minilink.app.dws; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.minilink.app.sink.ClickHouseSink; 5 | import com.minilink.constant.KafkaConstant; 6 | import com.minilink.pojo.VisitShortLinkLog; 7 | import com.minilink.util.DateTimeUtil; 8 | import com.minilink.util.FlinkKafkaUtil; 9 | import org.apache.flink.api.common.eventtime.WatermarkStrategy; 10 | import org.apache.flink.api.common.functions.MapFunction; 11 | import org.apache.flink.api.common.functions.ReduceFunction; 12 | import org.apache.flink.api.java.functions.KeySelector; 13 | import org.apache.flink.api.java.tuple.Tuple9; 14 | import org.apache.flink.streaming.api.datastream.*; 15 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 16 | import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction; 17 | import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows; 18 | import org.apache.flink.streaming.api.windowing.time.Time; 19 | import org.apache.flink.streaming.api.windowing.windows.TimeWindow; 20 | import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; 21 | import org.apache.flink.util.Collector; 22 | 23 | import java.time.Duration; 24 | 25 | /** 26 | * @Author: 徐志斌 27 | * @CreateTime: 2024-12-30 14:55 28 | * @Description: DWS 29 | * @Version: 1.0 30 | */ 31 | public class DwsClickLinkApp { 32 | public static final String SOURCE_TOPIC_WIDE_LOG = KafkaConstant.DWM_WIDE_LOG_TOPIC; 33 | public static final String GROUP_WIDE_LOG = KafkaConstant.DWS_WIDE_LOG_GROUP; 34 | public static final String SOURCE_TOPIC_UNIQUE_VISITOR = KafkaConstant.DWM_UNIQUE_VISITOR_TOPIC; 35 | public static final String GROUP_UNIQUE_VISITOR = KafkaConstant.DWS_UNIQUE_VISITOR_GROUP; 36 | 37 | public static void main(String[] args) throws Exception { 38 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 39 | FlinkKafkaConsumer wideLogConsumer = FlinkKafkaUtil.getKafkaConsumer(SOURCE_TOPIC_WIDE_LOG, GROUP_WIDE_LOG); 40 | DataStreamSource wideLogJsonStr = env.addSource(wideLogConsumer); 41 | FlinkKafkaConsumer uniqueVisitorConsumer = FlinkKafkaUtil.getKafkaConsumer(SOURCE_TOPIC_UNIQUE_VISITOR, GROUP_UNIQUE_VISITOR); 42 | DataStreamSource uniqueVisitorJsonStr = env.addSource(uniqueVisitorConsumer); 43 | 44 | SingleOutputStreamOperator wideLogDS = wideLogJsonStr.map( 45 | new MapFunction() { 46 | @Override 47 | public VisitShortLinkLog map(String msg) throws Exception { 48 | VisitShortLinkLog visitShortLinkLog = JSONUtil.toBean(msg, VisitShortLinkLog.class); 49 | visitShortLinkLog.setPv(1L); 50 | visitShortLinkLog.setUv(0L); 51 | return visitShortLinkLog; 52 | } 53 | } 54 | ); 55 | 56 | SingleOutputStreamOperator uniqueVisitorDS = uniqueVisitorJsonStr.map( 57 | new MapFunction() { 58 | @Override 59 | public VisitShortLinkLog map(String msg) throws Exception { 60 | VisitShortLinkLog visitShortLinkLog = JSONUtil.toBean(msg, VisitShortLinkLog.class); 61 | visitShortLinkLog.setPv(0L); 62 | visitShortLinkLog.setUv(1L); 63 | return visitShortLinkLog; 64 | } 65 | } 66 | ); 67 | 68 | DataStream unionDS = wideLogDS.union(uniqueVisitorDS); 69 | 70 | // 水位线策略:超过3s视为乱序,少于3s允许进入窗口计算 71 | SingleOutputStreamOperator waterMarkDS = unionDS.assignTimestampsAndWatermarks( 72 | WatermarkStrategy 73 | .forBoundedOutOfOrderness(Duration.ofSeconds(3)) 74 | .withTimestampAssigner((event, timestamp) -> DateTimeUtil.localDateTimeToTimeStamp(event.getVisitTime())) 75 | ); 76 | 77 | KeyedStream keyedStreamDS = waterMarkDS.keyBy( 78 | new KeySelector>() { 79 | @Override 80 | public Tuple9 getKey(VisitShortLinkLog log) throws Exception { 81 | return Tuple9.of( 82 | log.getAccountId(), log.getShortLinkCode(), 83 | log.getIp(), log.getProvince(), log.getCity(), 84 | log.getBrowserType(), log.getDeviceType(), 85 | log.getOsType(), log.getVisitorState() 86 | ); 87 | } 88 | } 89 | ); 90 | 91 | WindowedStream windowDS = keyedStreamDS.window(TumblingEventTimeWindows.of(Time.seconds(10))); 92 | 93 | SingleOutputStreamOperator reduceDS = windowDS.reduce( 94 | new ReduceFunction() { 95 | @Override 96 | public VisitShortLinkLog reduce(VisitShortLinkLog log1, VisitShortLinkLog log2) throws Exception { 97 | log1.setPv(log1.getPv() + log2.getPv()); 98 | log1.setUv(log1.getUv() + log2.getUv()); 99 | return log1; 100 | } 101 | }, 102 | 103 | new ProcessWindowFunction, TimeWindow>() { 104 | @Override 105 | public void process(Tuple9 tuple9, 106 | ProcessWindowFunction, 107 | TimeWindow>.Context context, Iterable iterable, Collector collector) throws Exception { 108 | String startTime = DateTimeUtil.format(context.window().getStart()); 109 | String endTime = DateTimeUtil.format(context.window().getEnd()); 110 | for (VisitShortLinkLog log : iterable) { 111 | log.setStartTime(startTime); 112 | log.setEndTime(endTime); 113 | } 114 | } 115 | } 116 | ); 117 | reduceDS.print(">>>>>>>>DWS-reduceDS"); 118 | reduceDS.addSink(ClickHouseSink.getJdbcSink("insert into link_visit_stats(account_id, short_link_code, ip, province, city, " + 119 | "browser_type, device_type, os_type, visitor_state, pv, uv, start_time, end_time) values(?,?,?,?,?,?,?,?,?,?,?,?,?)")); 120 | env.execute(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/app/func/DeviceMapFunction.java: -------------------------------------------------------------------------------- 1 | package com.minilink.app.func; 2 | 3 | import cn.hutool.json.JSONObject; 4 | import cn.hutool.json.JSONUtil; 5 | import com.minilink.pojo.VisitShortLinkLog; 6 | import com.minilink.util.DateTimeUtil; 7 | import com.minilink.util.UserAgentUtil; 8 | import org.apache.flink.api.common.functions.MapFunction; 9 | 10 | /** 11 | * @Author: 徐志斌 12 | * @CreateTime: 2024-12-30 11:17 13 | * @Description: DWM-设备信息 14 | * @Version: 1.0 15 | */ 16 | public class DeviceMapFunction implements MapFunction { 17 | @Override 18 | public VisitShortLinkLog map(String msg) { 19 | JSONObject jsonObj = JSONUtil.toBean(msg, JSONObject.class); 20 | String userAgentStr = jsonObj.getStr("userAgent"); 21 | String ip = jsonObj.getStr("ip"); 22 | String browserType = UserAgentUtil.getBrowserType(userAgentStr); 23 | String osType = UserAgentUtil.getOsType(userAgentStr); 24 | String deviceType = UserAgentUtil.getDeviceType(userAgentStr); 25 | String visitTimeStamp = jsonObj.getStr("visitTime"); 26 | String visitorState = jsonObj.getStr("visitorState"); 27 | VisitShortLinkLog msgLog = new VisitShortLinkLog(); 28 | msgLog.setIp(ip); 29 | msgLog.setUserAgent(userAgentStr); 30 | msgLog.setVisitorState(visitorState); 31 | msgLog.setBrowserType(browserType); 32 | msgLog.setOsType(osType); 33 | msgLog.setDeviceType(deviceType); 34 | msgLog.setVisitTime(DateTimeUtil.timeStampToLocalDateTime(Long.valueOf(visitTimeStamp))); 35 | return msgLog; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/app/func/LocationMapFunction.java: -------------------------------------------------------------------------------- 1 | package com.minilink.app.func; 2 | 3 | import com.minilink.pojo.VisitShortLinkLog; 4 | import com.minilink.util.AMapUtil; 5 | import org.apache.flink.api.common.functions.MapFunction; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * @Author: 徐志斌 11 | * @CreateTime: 2024-12-30 11:17 12 | * @Description: DWM-设备信息 13 | * @Version: 1.0 14 | */ 15 | public class LocationMapFunction implements MapFunction { 16 | @Override 17 | public VisitShortLinkLog map(VisitShortLinkLog wideLog) { 18 | Map locationMap = AMapUtil.getLocationByIp(wideLog.getIp()); 19 | wideLog.setProvince(locationMap.get("province")); 20 | wideLog.setCity(locationMap.get("city")); 21 | return wideLog; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/app/func/VisitorStateRichMapFunction.java: -------------------------------------------------------------------------------- 1 | package com.minilink.app.func; 2 | 3 | import cn.hutool.json.JSONObject; 4 | import cn.hutool.json.JSONUtil; 5 | import com.minilink.enums.VisitorStateEnum; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.apache.flink.api.common.functions.RichMapFunction; 8 | import org.apache.flink.api.common.state.StateTtlConfig; 9 | import org.apache.flink.api.common.state.ValueState; 10 | import org.apache.flink.api.common.state.ValueStateDescriptor; 11 | import org.apache.flink.api.common.time.Time; 12 | import org.apache.flink.configuration.Configuration; 13 | 14 | /** 15 | * @Author: 徐志斌 16 | * @CreateTime: 2024-12-24 17:19 17 | * @Description: DWD-新老访客 18 | * @Version: 1.0 19 | */ 20 | public class VisitorStateRichMapFunction extends RichMapFunction { 21 | private ValueState visitorState; 22 | 23 | @Override 24 | public void open(Configuration parameters) { 25 | ValueStateDescriptor stateDescriptor = new ValueStateDescriptor<>("visitorState", String.class); 26 | StateTtlConfig ttlConfig = StateTtlConfig.newBuilder(Time.days(30)).build(); 27 | stateDescriptor.enableTimeToLive(ttlConfig); 28 | visitorState = getRuntimeContext().getState(stateDescriptor); 29 | } 30 | 31 | @Override 32 | public String map(JSONObject jsonStr) throws Exception { 33 | String beforeTimeStr = visitorState.value(); 34 | String nowTimeStr = jsonStr.getStr("visitTime"); 35 | if (StringUtils.isNotEmpty(beforeTimeStr)) { 36 | jsonStr.set("visitorState", VisitorStateEnum.OLD.getCode()); 37 | } else { 38 | jsonStr.set("visitorState", VisitorStateEnum.NEW.getCode()); 39 | visitorState.update(nowTimeStr); 40 | } 41 | return JSONUtil.toJsonStr(jsonStr); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/app/func/VisitorUniqueRichFilterFunction.java: -------------------------------------------------------------------------------- 1 | package com.minilink.app.func; 2 | 3 | import com.minilink.pojo.VisitShortLinkLog; 4 | import com.minilink.util.DateTimeUtil; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.apache.flink.api.common.functions.RichFilterFunction; 7 | import org.apache.flink.api.common.state.StateTtlConfig; 8 | import org.apache.flink.api.common.state.ValueState; 9 | import org.apache.flink.api.common.state.ValueStateDescriptor; 10 | import org.apache.flink.api.common.time.Time; 11 | import org.apache.flink.configuration.Configuration; 12 | 13 | import java.io.IOException; 14 | 15 | /** 16 | * @Author: 徐志斌 17 | * @CreateTime: 2024-12-30 11:20 18 | * @Description: DWM-独立访客UV 19 | * @Version: 1.0 20 | */ 21 | public class VisitorUniqueRichFilterFunction extends RichFilterFunction { 22 | private ValueState uniqueState; 23 | 24 | @Override 25 | public void open(Configuration parameters) { 26 | ValueStateDescriptor stateDescriptor = new ValueStateDescriptor<>("uniqueState", String.class); 27 | StateTtlConfig ttlConfig = StateTtlConfig.newBuilder(Time.days(1)).build(); 28 | stateDescriptor.enableTimeToLive(ttlConfig); 29 | uniqueState = getRuntimeContext().getState(stateDescriptor); 30 | } 31 | 32 | @Override 33 | public boolean filter(VisitShortLinkLog wideLog) throws IOException { 34 | String visitTime = DateTimeUtil.format(wideLog.getVisitTime()); 35 | String beforeTime = uniqueState.value(); 36 | if (StringUtils.isEmpty(beforeTime)) { 37 | uniqueState.update(visitTime); 38 | return true; 39 | } else { 40 | return false; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/app/sink/ClickHouseSink.java: -------------------------------------------------------------------------------- 1 | package com.minilink.app.sink; 2 | 3 | import com.minilink.pojo.VisitShortLinkLog; 4 | import org.apache.flink.connector.jdbc.JdbcConnectionOptions; 5 | import org.apache.flink.connector.jdbc.JdbcExecutionOptions; 6 | import org.apache.flink.connector.jdbc.JdbcSink; 7 | import org.apache.flink.connector.jdbc.JdbcStatementBuilder; 8 | import org.apache.flink.streaming.api.functions.sink.SinkFunction; 9 | 10 | /** 11 | * @Author: 徐志斌 12 | * @CreateTime: 2024-12-31 14:08 13 | * @Description: ClickHouse下游 14 | * @Version: 1.0 15 | */ 16 | public class ClickHouseSink { 17 | public static SinkFunction getJdbcSink(String sql) { 18 | JdbcStatementBuilder statementBuilder = (statement, param) -> { 19 | statement.setObject(1, param.getAccountId()); 20 | statement.setObject(2, param.getShortLinkCode()); 21 | statement.setObject(3, param.getIp()); 22 | statement.setObject(4, param.getProvince()); 23 | statement.setObject(5, param.getCity()); 24 | statement.setObject(6, param.getBrowserType()); 25 | statement.setObject(7, param.getDeviceType()); 26 | statement.setObject(8, param.getOsType()); 27 | statement.setObject(9, param.getVisitorState()); 28 | }; 29 | JdbcExecutionOptions executionOptions = new JdbcExecutionOptions.Builder() 30 | .withBatchSize(3) 31 | .build(); 32 | JdbcConnectionOptions connectionOptions = new JdbcConnectionOptions.JdbcConnectionOptionsBuilder() 33 | .withUrl("jdbc:clickhouse://124.70.62.67:8123/default") 34 | .withDriverName("ru.yandex.clickhouse.ClickHouseDriver") 35 | .withUsername("default") 36 | .build(); 37 | return JdbcSink.sink(sql, statementBuilder, executionOptions, connectionOptions); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/constant/KafkaConstant.java: -------------------------------------------------------------------------------- 1 | package com.minilink.constant; 2 | 3 | /** 4 | * @Author: 徐志斌 5 | * @CreateTime: 2024-12-20 10:49 6 | * @Description: Kafka 常量 7 | * @Version: 1.0 8 | */ 9 | public class KafkaConstant { 10 | public static final String KAFKA_SERVER = "localhost:9092"; 11 | 12 | /** 13 | * ODS 14 | */ 15 | public static final String ODS_CLICK_LINK_TOPIC = "ods_click_link_topic"; 16 | 17 | /** 18 | * DWD 19 | */ 20 | public static final String DWD_CLICK_LINK_TOPIC = "dwd_click_link_topic"; 21 | 22 | /** 23 | * DWM 24 | */ 25 | public static final String DWM_WIDE_LOG_TOPIC = "dwm_wide_log_topic"; 26 | public static final String DWM_UNIQUE_VISITOR_TOPIC = "dwm_unique_visitor_topic"; 27 | 28 | /** 29 | * 消费组 30 | */ 31 | public static final String DWD_CLICK_LINK_GROUP = "dwd_click_link_group"; 32 | public static final String DWM_WIDE_LOG_GROUP = "dwm_wide_log_group"; 33 | public static final String DWM_UNIQUE_VISITOR_GROUP = "dwm_unique_visitor_group"; 34 | public static final String DWS_WIDE_LOG_GROUP = "dws_wide_log_group"; 35 | public static final String DWS_UNIQUE_VISITOR_GROUP = "dws_unique_visitor_group"; 36 | } 37 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/enums/VisitorStateEnum.java: -------------------------------------------------------------------------------- 1 | package com.minilink.enums; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | /** 8 | * @Author: 徐志斌 9 | * @CreateTime: 2024-12-23 09:57 10 | * @Description: 访客状态枚举 11 | * @Version: 1.0 12 | */ 13 | @Getter 14 | @AllArgsConstructor 15 | public enum VisitorStateEnum { 16 | NEW("0", "新访客"), 17 | OLD("1", "老访客"); 18 | 19 | private String code; 20 | private String msg; 21 | } 22 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/pojo/VisitShortLinkLog.java: -------------------------------------------------------------------------------- 1 | package com.minilink.pojo; 2 | 3 | import com.minilink.enums.VisitorStateEnum; 4 | import lombok.Data; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * @Author: 徐志斌 10 | * @CreateTime: 2024-12-20 11:21 11 | * @Description: 访问短链接行为日志数据 12 | * @Version: 1.0 13 | */ 14 | @Data 15 | public class VisitShortLinkLog { 16 | /** 17 | * 账号id 18 | */ 19 | private Long accountId; 20 | 21 | /** 22 | * 短链接码 23 | */ 24 | private String shortLinkCode; 25 | 26 | /** 27 | * ip 28 | */ 29 | private String ip; 30 | 31 | /** 32 | * 省 33 | */ 34 | private String province; 35 | 36 | /** 37 | * 市 38 | */ 39 | private String city; 40 | 41 | /** 42 | * 浏览器指纹 43 | */ 44 | private String userAgent; 45 | 46 | /** 47 | * 设备类型 48 | */ 49 | private String deviceType; 50 | 51 | /** 52 | * 操作系统类型 53 | */ 54 | private String osType; 55 | 56 | /** 57 | * 浏览器类型 58 | */ 59 | private String browserType; 60 | 61 | /** 62 | * 访客状态 63 | * 64 | * @see VisitorStateEnum 65 | */ 66 | private String visitorState; 67 | 68 | /** 69 | * 访问时间 70 | */ 71 | private LocalDateTime visitTime; 72 | 73 | /** 74 | * pv 浏览量 75 | */ 76 | private Long pv; 77 | 78 | /** 79 | * uv 访客量 80 | */ 81 | private Long uv; 82 | 83 | /** 84 | * 开始时间 85 | */ 86 | private String startTime; 87 | 88 | /** 89 | * 结束时间 90 | */ 91 | private String endTime; 92 | } 93 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/util/AMapUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | import cn.hutool.json.JSONObject; 4 | import cn.hutool.json.JSONUtil; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * @Author: 徐志斌 11 | * @CreateTime: 2024-12-27 10:55 12 | * @Description: 高德地图工具类 13 | * @Version: 1.0 14 | */ 15 | public class AMapUtil { 16 | private static String AMAP_SECRET = "9269ad4b210d90de42f6104e14231bbf"; 17 | private static String IP_LOCATION_API = "https://restapi.amap.com/v3/ip?key=" + AMAP_SECRET + "&ip=%s"; 18 | 19 | public static Map getLocationByIp(String ip) { 20 | String restApi = String.format(IP_LOCATION_API, ip); 21 | String locationStr = OkHttpUtil.get(restApi); 22 | JSONObject jsonObj = JSONUtil.toBean(locationStr, JSONObject.class); 23 | Map resultMap = new HashMap<>(); 24 | resultMap.put("province", jsonObj.get("province") == null ? null : jsonObj.get("province").toString()); 25 | resultMap.put("city", jsonObj.get("city") == null ? null : jsonObj.get("city").toString()); 26 | return resultMap; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/util/DateTimeUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | import java.time.LocalDateTime; 4 | import java.time.ZoneId; 5 | import java.time.format.DateTimeFormatter; 6 | import java.util.Date; 7 | 8 | /** 9 | * @Author: 徐志斌 10 | * @CreateTime: 2024-12-03 11:23 11 | * @Description: 日期转换工具类 12 | * @Version: 1.0 13 | */ 14 | public class DateTimeUtil { 15 | private static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss"; 16 | private static final DateTimeFormatter DEFAULT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_PATTERN); 17 | private static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault(); 18 | 19 | /** 20 | * LocalDateTime 转 String,指定日期格式 21 | */ 22 | public static String format(LocalDateTime localDateTime, String pattern) { 23 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); 24 | return formatter.format(localDateTime.atZone(DEFAULT_ZONE_ID)); 25 | } 26 | 27 | /** 28 | * LocalDateTime 转 String,默认日期格式 29 | */ 30 | public static String format(LocalDateTime localDateTime) { 31 | return DEFAULT_DATE_TIME_FORMATTER.format(localDateTime.atZone(DEFAULT_ZONE_ID)); 32 | } 33 | 34 | /** 35 | * Date 转 String,指定日期格式 36 | */ 37 | public static String format(Date time, String pattern) { 38 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); 39 | return formatter.format(time.toInstant().atZone(DEFAULT_ZONE_ID)); 40 | } 41 | 42 | /** 43 | * Date 转 String,默认日期格式 44 | */ 45 | public static String format(Date time) { 46 | return DEFAULT_DATE_TIME_FORMATTER.format(time.toInstant().atZone(DEFAULT_ZONE_ID)); 47 | } 48 | 49 | /** 50 | * timestamp 转 字符串,默认日期格式 51 | */ 52 | public static String format(long timestamp) { 53 | return DEFAULT_DATE_TIME_FORMATTER.format(new Date(timestamp).toInstant().atZone(DEFAULT_ZONE_ID)); 54 | } 55 | 56 | /** 57 | * timestamp 转 LocalDateTime,默认日期格式 58 | */ 59 | public static LocalDateTime timeStampToLocalDateTime(long timestamp) { 60 | return LocalDateTime.ofInstant(new Date(timestamp).toInstant(), DEFAULT_ZONE_ID); 61 | } 62 | 63 | /** 64 | * LocalDateTime 转 timestamp 65 | */ 66 | public static Long localDateTimeToTimeStamp(LocalDateTime time) { 67 | return time.atZone(DEFAULT_ZONE_ID).toInstant().getEpochSecond(); 68 | } 69 | } -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/util/FlinkKafkaUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | import com.minilink.constant.KafkaConstant; 4 | import org.apache.flink.api.common.serialization.SimpleStringSchema; 5 | import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; 6 | import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer; 7 | import org.apache.kafka.clients.consumer.ConsumerConfig; 8 | 9 | import java.util.Properties; 10 | 11 | /** 12 | * @Author: 徐志斌 13 | * @CreateTime: 2024-12-23 15:55 14 | * @Description: Flink Kafka 工具类 15 | * @Version: 1.0 16 | */ 17 | public class FlinkKafkaUtil { 18 | 19 | /** 20 | * 获取 Flink Kafka 生产者 21 | * 22 | * @param topic 主题 23 | * @return 生产者 24 | */ 25 | public static FlinkKafkaProducer getKafkaProducer(String topic) { 26 | return new FlinkKafkaProducer(KafkaConstant.KAFKA_SERVER, topic, new SimpleStringSchema()); 27 | } 28 | 29 | /** 30 | * 获取 Flink Kafka 消费者 31 | * 32 | * @param topic 主题 33 | * @param groupId 消费组 34 | * @return 消费者 35 | */ 36 | public static FlinkKafkaConsumer getKafkaConsumer(String topic, String groupId) { 37 | Properties props = new Properties(); 38 | props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, groupId); 39 | props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KafkaConstant.KAFKA_SERVER); 40 | return new FlinkKafkaConsumer(topic, new SimpleStringSchema(), props); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/util/OkHttpUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | import cn.hutool.json.JSONObject; 4 | import lombok.extern.slf4j.Slf4j; 5 | import okhttp3.*; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * @Author: 徐志斌 11 | * @CreateTime: 2024-12-27 11:38 12 | * @Description: OKHTTP 工具类 13 | * @Version: 1.0 14 | */ 15 | @Slf4j 16 | public class OkHttpUtil { 17 | public static final String MEDIA_TYPE_JSON = "application/json; charset=utf-8"; 18 | 19 | public static OkHttpClient getOkHttpClient() { 20 | return getOkHttpClient(60, 60, 60); 21 | } 22 | 23 | public static OkHttpClient getOkHttpClient(int connectTimeout, int readTimeOut, int writeTimeOut) { 24 | OkHttpClient.Builder builder = new okhttp3.OkHttpClient().newBuilder(); 25 | builder.connectTimeout(connectTimeout, TimeUnit.SECONDS); 26 | builder.readTimeout(readTimeOut, TimeUnit.SECONDS); 27 | builder.writeTimeout(writeTimeOut, TimeUnit.SECONDS); 28 | return builder.build(); 29 | } 30 | 31 | public static String get(OkHttpClient okHttpClient, String url, Headers headers) { 32 | log.info("okHttpClient get url:{}.", url); 33 | Request request = new Request.Builder().url(url).headers(headers).get().build(); 34 | 35 | String responseData = request(okHttpClient, url, request); 36 | log.info("okHttpClient get url:{},request responseData====> {}", url, responseData); 37 | return responseData; 38 | } 39 | 40 | public static String get(OkHttpClient okHttpClient, String url) { 41 | Headers headers = new Headers.Builder().build(); 42 | return get(okHttpClient, url, headers); 43 | } 44 | 45 | public static String get(String url) { 46 | OkHttpClient okHttpClient = getOkHttpClient(); 47 | Headers headers = new Headers.Builder().build(); 48 | return get(okHttpClient, url, headers); 49 | } 50 | 51 | public static String post(OkHttpClient okHttpClient, String url, JSONObject bodyJson, Headers headers) { 52 | log.info("okHttpClient post url:{}, body====> {}", url, bodyJson); 53 | MediaType mediaTypeJson = MediaType.parse(MEDIA_TYPE_JSON); 54 | RequestBody requestBody = RequestBody.create(mediaTypeJson, bodyJson.toString()); 55 | Request request = new Request.Builder().url(url).headers(headers).post(requestBody).build(); 56 | 57 | String responseData = request(okHttpClient, url, request); 58 | log.info("okHttpClient post url:{},post responseData====> {}", url, responseData); 59 | return responseData; 60 | } 61 | 62 | public static String post(OkHttpClient okHttpClient, String url, JSONObject bodyJson) { 63 | Headers headers = new Headers.Builder().build(); 64 | return post(okHttpClient, url, bodyJson, headers); 65 | } 66 | 67 | public static String post(String url, JSONObject bodyJson) { 68 | OkHttpClient okHttpClient = getOkHttpClient(); 69 | Headers headers = new Headers.Builder().build(); 70 | return post(okHttpClient, url, bodyJson, headers); 71 | } 72 | 73 | public static String request(OkHttpClient okHttpClient, String url, Request request) { 74 | String responseData = ""; 75 | try (Response response = okHttpClient.newCall(request).execute()) { 76 | if (response != null && response.body() != null) { 77 | return response.body().string(); 78 | } 79 | } catch (Exception e) { 80 | log.error("okHttpClient getResponse error.url:{}", url, e); 81 | } 82 | return responseData; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /mini-link-flink/src/main/java/com/minilink/util/UserAgentUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | import eu.bitwalker.useragentutils.Browser; 4 | import eu.bitwalker.useragentutils.OperatingSystem; 5 | import eu.bitwalker.useragentutils.UserAgent; 6 | 7 | /** 8 | * @Author: 徐志斌 9 | * @CreateTime: 2024-12-17 16:15 10 | * @Description: 浏览器指纹解析-工具类 11 | * @Version: 1.0 12 | */ 13 | public class UserAgentUtil { 14 | public static UserAgent getUserAgent(String userAgentStr) { 15 | return UserAgent.parseUserAgentString(userAgentStr); 16 | } 17 | 18 | public static String getBrowserType(String userAgentStr) { 19 | Browser browser = getUserAgent(userAgentStr).getBrowser(); 20 | return browser.getGroup().getName(); 21 | } 22 | 23 | public static String getOsType(String userAgentStr) { 24 | OperatingSystem operatingSystem = getUserAgent(userAgentStr).getOperatingSystem(); 25 | return operatingSystem.getGroup().getName(); 26 | } 27 | 28 | public static String getDeviceType(String userAgentStr) { 29 | OperatingSystem operatingSystem = getUserAgent(userAgentStr).getOperatingSystem(); 30 | return operatingSystem.getDeviceType().getName(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mini-link-gateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:17 2 | MAINTAINER 徐志斌 3 | ADD mini-link-gateway.jar /mini-link-gateway.jar 4 | EXPOSE 9000 5 | ENTRYPOINT ["java", "-jar", "springboot.jar"] -------------------------------------------------------------------------------- /mini-link-gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | com.minilink 6 | mini-link 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | 4.0.0 11 | com.minilink 12 | mini-link-gateway 13 | 0.0.1-SNAPSHOT 14 | mini-link-gateway 15 | 16 | 17 | 18 | com.alibaba.cloud 19 | spring-cloud-starter-alibaba-nacos-discovery 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-gateway 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-starter-loadbalancer 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-maven-plugin 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /mini-link-gateway/src/main/java/com/minilink/MiniLinkGatewayApplication.java: -------------------------------------------------------------------------------- 1 | package com.minilink; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 6 | 7 | @SpringBootApplication(exclude= {DataSourceAutoConfiguration.class}) 8 | public class MiniLinkGatewayApplication { 9 | 10 | public static void main(String[] args) { 11 | SpringApplication.run(MiniLinkGatewayApplication.class, args); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /mini-link-gateway/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | gateway: 4 | routes: 5 | - id: mini-link-user 6 | uri: lb://mini-link-user 7 | predicates: 8 | - Path=/user/** 9 | 10 | - id: mini-link-core 11 | uri: lb://mini-link-core 12 | predicates: 13 | - Path=/url/**,/** -------------------------------------------------------------------------------- /mini-link-gateway/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | spring: 4 | application: 5 | name: mini-link-gateway 6 | profiles: 7 | active: dev 8 | -------------------------------------------------------------------------------- /mini-link-gateway/src/main/resources/deploy/deployment-gateway.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mini-link-gateway-deployment 5 | namespace: mini-link-namespace 6 | labels: 7 | app: mini-link-gateway-label 8 | spec: 9 | replicas: 3 10 | selector: 11 | matchLabels: 12 | app: mini-link-gateway-label 13 | template: 14 | metadata: 15 | labels: 16 | app: mini-link-gateway-label 17 | spec: 18 | containers: 19 | - name: mini-link-gateway 20 | image: 镜像仓库IP:端口/minilink/mini-link-gateway:1.0 21 | imagePullPolicy: Always 22 | ports: 23 | - containerPort: 8080 24 | resources: 25 | requests: 26 | memory: 300Mi 27 | cpu: 200m 28 | limits: 29 | memory: 500Mi 30 | cpu: 400m 31 | readinessProbe: 32 | tcpSocket: 33 | port: 8080 34 | initialDelaySeconds: 60 35 | periodSeconds: 10 36 | livenessProbe: 37 | tcpSocket: 38 | port: 8080 39 | initialDelaySeconds: 60 40 | periodSeconds: 10 -------------------------------------------------------------------------------- /mini-link-gateway/src/main/resources/deploy/ingress-nginx.yml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: mini-link-ingress 5 | namespace: mini-link-namespace 6 | spec: 7 | ingressClassName: nginx 8 | rules: 9 | - host: minilink.com 10 | http: 11 | paths: 12 | - path: / 13 | pathType: Prefix 14 | backend: 15 | service: 16 | name: mini-link-frontend-service 17 | port: 18 | number: 80 -------------------------------------------------------------------------------- /mini-link-gateway/src/main/resources/deploy/service-gateway.yml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: backend-service 5 | namespace: mini-link-namespace 6 | labels: 7 | app: backend-label 8 | spec: 9 | selector: 10 | app: backend-label 11 | type: ClusterIP 12 | ports: 13 | - protocol: TCP 14 | port: 8080 15 | targetPort: 8080 16 | -------------------------------------------------------------------------------- /mini-link-user/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:17 2 | MAINTAINER 徐志斌 3 | ADD mini-link-user.jar /mini-link-user.jar 4 | EXPOSE 9002 5 | ENTRYPOINT ["java", "-jar", "mini-link-user.jar"] -------------------------------------------------------------------------------- /mini-link-user/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | com.minilink 6 | mini-link 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | 4.0.0 11 | com.minilink 12 | mini-link-user 13 | 0.0.1-SNAPSHOT 14 | mini-link-user 15 | 16 | 17 | 18 | com.minilink 19 | mini-link-common 20 | 0.0.1-SNAPSHOT 21 | 22 | 23 | com.baomidou 24 | mybatis-plus-generator 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-freemarker 29 | 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-mail 35 | 36 | 37 | com.baomidou 38 | kaptcha-spring-boot-starter 39 | 40 | 41 | io.minio 42 | minio 43 | 44 | 45 | 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-maven-plugin 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/MiniLinkUserApplication.java: -------------------------------------------------------------------------------- 1 | package com.minilink; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cache.annotation.EnableCaching; 7 | import org.springframework.scheduling.annotation.EnableAsync; 8 | 9 | @EnableAsync 10 | @EnableCaching 11 | @MapperScan("com.minilink.mapper") 12 | @SpringBootApplication 13 | public class MiniLinkUserApplication { 14 | public static void main(String[] args) { 15 | SpringApplication.run(MiniLinkUserApplication.class, args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/config/CaptchaConfig.java: -------------------------------------------------------------------------------- 1 | package com.minilink.config; 2 | 3 | import com.google.code.kaptcha.Constants; 4 | import com.google.code.kaptcha.impl.DefaultKaptcha; 5 | import com.google.code.kaptcha.util.Config; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import java.util.Properties; 10 | 11 | /** 12 | * @Author: 徐志斌 13 | * @CreateTime: 2024-12-6 20:44 14 | * @Description: 图片验证码 15 | * @Version: 1.0 16 | */ 17 | @Configuration 18 | public class CaptchaConfig { 19 | @Bean 20 | public DefaultKaptcha kaptcha() { 21 | Properties properties = new Properties(); 22 | // properties.setProperty(Constants.KAPTCHA_BORDER, "yes"); 23 | // properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "220,220,220"); 24 | // //properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "38,29,12"); 25 | // properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "147"); 26 | // properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "34"); 27 | // properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "25"); 28 | // //properties.setProperty(Constants.KAPTCHA_SESSION_KEY, "code"); 29 | //验证码个数 30 | properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); 31 | // properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Courier"); 32 | //字体间隔 33 | properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "8"); 34 | //干扰线颜色 35 | // properties.setProperty(Constants.KAPTCHA_NOISE_COLOR, "white"); 36 | //干扰实现类 37 | properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); 38 | //图片样式 39 | properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.WaterRipple"); 40 | //文字来源 41 | properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789"); 42 | Config config = new Config(properties); 43 | DefaultKaptcha kaptcha = new DefaultKaptcha(); 44 | kaptcha.setConfig(config); 45 | return kaptcha; 46 | } 47 | } -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/config/MinioConfig.java: -------------------------------------------------------------------------------- 1 | package com.minilink.config; 2 | 3 | import io.minio.MinioClient; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Bean; 6 | 7 | /** 8 | * @Author: 徐志斌 9 | * @CreateTime: 2024-12-19 16:25 10 | * @Description: MinIO 配置类 11 | * @Version: 1.0 12 | */ 13 | public class MinioConfig { 14 | @Value("${storage.minio.endpoint}") 15 | private String minioEndpoint; 16 | @Value("${storage.minio.access-key}") 17 | private String minioAccessKey; 18 | @Value("${storage.minio.secret-key}") 19 | private String minioSecretKey; 20 | 21 | @Bean 22 | public MinioClient minioClient() { 23 | return MinioClient.builder() 24 | .endpoint(minioEndpoint) 25 | .credentials(minioAccessKey, minioSecretKey) 26 | .build(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/controller/UserAssistController.java: -------------------------------------------------------------------------------- 1 | package com.minilink.controller; 2 | 3 | import com.minilink.annotation.NoLogin; 4 | import com.minilink.enums.BusinessCodeEnum; 5 | import com.minilink.service.UserAssistService; 6 | import com.minilink.util.resp.R; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.tags.Tag; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | import org.springframework.web.multipart.MultipartFile; 15 | 16 | import java.io.UnsupportedEncodingException; 17 | import java.security.NoSuchAlgorithmException; 18 | 19 | /** 20 | * @Author 徐志斌 21 | * @Date: 2024/12/6 21:35 22 | * @Version 1.0 23 | * @Description: 账号辅助相关控制器 24 | */ 25 | @Tag(name = "账号辅助") 26 | @RestController 27 | public class UserAssistController { 28 | @Autowired 29 | private UserAssistService assistService; 30 | 31 | @NoLogin 32 | @Operation(summary = "图片验证码") 33 | @GetMapping("/captcha") 34 | public void captcha() throws UnsupportedEncodingException, NoSuchAlgorithmException { 35 | assistService.captcha(); 36 | } 37 | 38 | @NoLogin 39 | @Operation(summary = "发送邮件") 40 | @PostMapping("/email/{type}/{email}") 41 | public R sendEmail(@PathVariable Integer type, @PathVariable String email) { 42 | assistService.sendEmail(type, email); 43 | return R.out(BusinessCodeEnum.SUCCESS); 44 | } 45 | 46 | @Operation(summary = "上传文件") 47 | @PostMapping("/upload/{type}") 48 | public R uploadFile(@PathVariable Integer type, MultipartFile file) { 49 | assistService.uploadFile(type, file); 50 | return R.out(BusinessCodeEnum.SUCCESS); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/controller/UserFormController.java: -------------------------------------------------------------------------------- 1 | package com.minilink.controller; 2 | 3 | import com.minilink.annotation.NoLogin; 4 | import com.minilink.enums.BusinessCodeEnum; 5 | import com.minilink.pojo.dto.LoginDTO; 6 | import com.minilink.pojo.dto.RegisterDTO; 7 | import com.minilink.service.UserFormService; 8 | import com.minilink.util.resp.R; 9 | import io.swagger.v3.oas.annotations.Operation; 10 | import io.swagger.v3.oas.annotations.tags.Tag; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import java.io.UnsupportedEncodingException; 17 | import java.security.NoSuchAlgorithmException; 18 | import java.util.Map; 19 | 20 | /** 21 | * @Author 徐志斌 22 | * @Date: 2024/12/6 21:35 23 | * @Version 1.0 24 | * @Description: 账号表单相关控制器 25 | */ 26 | @Tag(name = "账号表单") 27 | @RestController 28 | public class UserFormController { 29 | @Autowired 30 | private UserFormService formService; 31 | 32 | @NoLogin 33 | @Operation(summary = "注册账号") 34 | @PostMapping("/register") 35 | public R register(@RequestBody RegisterDTO registerDTO) throws UnsupportedEncodingException, NoSuchAlgorithmException { 36 | formService.register(registerDTO); 37 | return R.out(BusinessCodeEnum.SUCCESS); 38 | } 39 | 40 | @NoLogin 41 | @Operation(summary = "登录账号") 42 | @PostMapping("/login") 43 | public R login(@RequestBody LoginDTO loginDTO) throws UnsupportedEncodingException, NoSuchAlgorithmException { 44 | Map resultMap = formService.login(loginDTO); 45 | return R.out(BusinessCodeEnum.SUCCESS, resultMap); 46 | } 47 | 48 | @Operation(summary = "修改密码") 49 | @PostMapping("/updatePwd") 50 | public R forget() { 51 | return R.out(BusinessCodeEnum.SUCCESS); 52 | } 53 | 54 | @NoLogin 55 | @Operation(summary = "找回密码") 56 | @PostMapping("/find") 57 | public R findBack() { 58 | return R.out(BusinessCodeEnum.SUCCESS); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/controller/UserInfoController.java: -------------------------------------------------------------------------------- 1 | package com.minilink.controller; 2 | 3 | import com.minilink.enums.BusinessCodeEnum; 4 | import com.minilink.pojo.vo.UserVO; 5 | import com.minilink.service.UserInfoService; 6 | import com.minilink.util.resp.R; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.tags.Tag; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | /** 16 | * @Author 徐志斌 17 | * @Date: 2024/12/6 21:35 18 | * @Version 1.0 19 | * @Description: 账号信息相关控制器 20 | */ 21 | @Tag(name = "账号信息") 22 | @RestController 23 | public class UserInfoController { 24 | @Autowired 25 | private UserInfoService userService; 26 | 27 | @Operation(summary = "查询账户信息") 28 | @GetMapping("/info/{accountId}") 29 | public R getUserInfo(@PathVariable Long accountId) { 30 | UserVO userVO = userService.getUserInfo(accountId); 31 | return R.out(BusinessCodeEnum.SUCCESS, userVO); 32 | } 33 | 34 | @Operation(summary = "修改用户信息") 35 | @PostMapping("/update") 36 | public R updateUserInfo() { 37 | return R.out(BusinessCodeEnum.SUCCESS); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/mapper/LinkUserMapper.java: -------------------------------------------------------------------------------- 1 | package com.minilink.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.minilink.pojo.po.LinkUser; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | * @author 徐志斌 12 | * @since 2024-12-06 13 | */ 14 | public interface LinkUserMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/service/UserAssistService.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service; 2 | 3 | import org.springframework.web.multipart.MultipartFile; 4 | 5 | import java.io.UnsupportedEncodingException; 6 | import java.security.NoSuchAlgorithmException; 7 | 8 | /** 9 | *

10 | * 服务类 11 | *

12 | * 13 | * @author 徐志斌 14 | * @since 2024-12-06 15 | */ 16 | public interface UserAssistService { 17 | /** 18 | * 图片验证码 19 | */ 20 | void captcha() throws UnsupportedEncodingException, NoSuchAlgorithmException; 21 | 22 | /** 23 | * 获取客户端图片验证码 Redis Key 24 | */ 25 | String getCaptchaKey() throws UnsupportedEncodingException, NoSuchAlgorithmException; 26 | 27 | /** 28 | * 发送邮件 29 | * 30 | * @param type 类型 31 | * @param email 邮箱 32 | */ 33 | void sendEmail(Integer type, String email); 34 | 35 | /** 36 | * 上传文件 37 | * 38 | * @param type 类型 39 | * @param avatarFile 文件 40 | */ 41 | void uploadFile(Integer type, MultipartFile avatarFile); 42 | } 43 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/service/UserFormService.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service; 2 | 3 | import com.minilink.pojo.dto.LoginDTO; 4 | import com.minilink.pojo.dto.RegisterDTO; 5 | 6 | import java.io.UnsupportedEncodingException; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.util.Map; 9 | 10 | /** 11 | *

12 | * 服务类 13 | *

14 | * 15 | * @author 徐志斌 16 | * @since 2024-12-06 17 | */ 18 | public interface UserFormService { 19 | /** 20 | * 注册账号 21 | * 22 | * @param registerDTO 注册表单参数 23 | */ 24 | void register(RegisterDTO registerDTO) throws UnsupportedEncodingException, NoSuchAlgorithmException; 25 | 26 | /** 27 | * 登录系统 28 | * 29 | * @param loginDTO 登录表单参数 30 | * @return 登录账户相关信息 31 | */ 32 | Map login(LoginDTO loginDTO) throws UnsupportedEncodingException, NoSuchAlgorithmException; 33 | } 34 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/service/UserInfoService.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service; 2 | 3 | import com.minilink.pojo.vo.UserVO; 4 | 5 | /** 6 | *

7 | * 服务类 8 | *

9 | * 10 | * @author 徐志斌 11 | * @since 2024-12-06 12 | */ 13 | public interface UserInfoService { 14 | /** 15 | * 根据 account_id 查询用户信息 16 | * 17 | * @param accountId 账号 18 | * @return 用户信息 19 | */ 20 | UserVO getUserInfo(Long accountId); 21 | } 22 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/service/impl/UserAssistServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 4 | import com.google.code.kaptcha.Producer; 5 | import com.minilink.constant.RedisConstant; 6 | import com.minilink.constant.RegexConstant; 7 | import com.minilink.enums.BusinessCodeEnum; 8 | import com.minilink.exception.BusinessException; 9 | import com.minilink.pojo.entity.EmailParamEntity; 10 | import com.minilink.service.UserAssistService; 11 | import com.minilink.strategy.email.AbstractEmailStrategy; 12 | import com.minilink.strategy.email.EmailStrategyFactory; 13 | import com.minilink.util.EncryptUtil; 14 | import com.minilink.util.HttpServletUtil; 15 | import com.minilink.util.IpUtil; 16 | import jakarta.servlet.ServletOutputStream; 17 | import jakarta.servlet.http.HttpServletRequest; 18 | import jakarta.servlet.http.HttpServletResponse; 19 | import lombok.extern.slf4j.Slf4j; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.data.redis.core.RedisTemplate; 22 | import org.springframework.stereotype.Service; 23 | import org.springframework.web.multipart.MultipartFile; 24 | 25 | import javax.imageio.ImageIO; 26 | import java.awt.image.BufferedImage; 27 | import java.io.IOException; 28 | import java.io.UnsupportedEncodingException; 29 | import java.security.NoSuchAlgorithmException; 30 | import java.util.concurrent.TimeUnit; 31 | 32 | /** 33 | *

34 | * 服务实现类 35 | *

36 | * 37 | * @author 徐志斌 38 | * @since 2024-12-06 39 | */ 40 | @Slf4j 41 | @Service 42 | public class UserAssistServiceImpl implements UserAssistService { 43 | @Autowired 44 | private Producer captchaProducer; 45 | @Autowired 46 | private RedisTemplate redisTemplate; 47 | 48 | @Override 49 | public void captcha() throws UnsupportedEncodingException, NoSuchAlgorithmException { 50 | String captchaText = captchaProducer.createText(); 51 | String captchaKey = this.getCaptchaKey(); 52 | redisTemplate.opsForValue().set(captchaKey, captchaText, 3, TimeUnit.MINUTES); 53 | HttpServletResponse response = HttpServletUtil.getResponse(); 54 | BufferedImage bufferedImage = captchaProducer.createImage(captchaText); 55 | try (ServletOutputStream outputStream = response.getOutputStream()) { 56 | ImageIO.write(bufferedImage, "jpg", outputStream); 57 | outputStream.flush(); 58 | } catch (IOException e) { 59 | log.error("----------图片验证码生成失败:{}----------", e.getMessage()); 60 | } 61 | } 62 | 63 | @Override 64 | public String getCaptchaKey() throws UnsupportedEncodingException, NoSuchAlgorithmException { 65 | HttpServletRequest request = HttpServletUtil.getRequest(); 66 | String userAgent = request.getHeader("User-Agent"); 67 | return RedisConstant.CAPTCHA_KEY + EncryptUtil.md5(userAgent + IpUtil.getIpAddr()); 68 | } 69 | 70 | @Override 71 | public void sendEmail(Integer type, String email) { 72 | if (!email.matches(RegexConstant.REGEX_EMAIL_FORMAT)) { 73 | throw new BusinessException(BusinessCodeEnum.CODE_EMAIL_ERROR); 74 | } 75 | String emailCheckKey = (String) redisTemplate.opsForValue().get(RedisConstant.EMAIL_CHECK_KEY + email); 76 | if (StringUtils.isNotBlank(emailCheckKey)) { 77 | throw new BusinessException(BusinessCodeEnum.OPS_REPEAT); 78 | } 79 | EmailParamEntity paramEntity = new EmailParamEntity(); 80 | paramEntity.setEmail(email); 81 | AbstractEmailStrategy emailHandler = EmailStrategyFactory.getStrategyHandler(type); 82 | emailHandler.sendEmail(paramEntity); 83 | } 84 | 85 | @Override 86 | public void uploadFile(Integer type, MultipartFile file) { 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/service/impl/UserFormServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; 4 | import com.minilink.adapter.UserAdapter; 5 | import com.minilink.constant.RedisConstant; 6 | import com.minilink.enums.BusinessCodeEnum; 7 | import com.minilink.exception.BusinessException; 8 | import com.minilink.pojo.dto.LoginDTO; 9 | import com.minilink.pojo.dto.RegisterDTO; 10 | import com.minilink.pojo.po.LinkUser; 11 | import com.minilink.service.UserAssistService; 12 | import com.minilink.service.UserFormService; 13 | import com.minilink.store.LinkUserStore; 14 | import com.minilink.util.EncryptUtil; 15 | import com.minilink.util.JwtUtil; 16 | import com.minilink.util.RandomUtil; 17 | import com.minilink.util.SnowFlakeUtil; 18 | import lombok.extern.slf4j.Slf4j; 19 | import org.springframework.beans.factory.annotation.Autowired; 20 | import org.springframework.data.redis.core.RedisTemplate; 21 | import org.springframework.stereotype.Service; 22 | 23 | import java.io.UnsupportedEncodingException; 24 | import java.security.NoSuchAlgorithmException; 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | 28 | /** 29 | *

30 | * 服务实现类 31 | *

32 | * 33 | * @author 徐志斌 34 | * @since 2024-12-06 35 | */ 36 | @Slf4j 37 | @Service 38 | public class UserFormServiceImpl implements UserFormService { 39 | @Autowired 40 | private RedisTemplate redisTemplate; 41 | @Autowired 42 | private LinkUserStore userStore; 43 | @Autowired 44 | private UserAssistService assistService; 45 | 46 | @Override 47 | public void register(RegisterDTO registerDTO) throws UnsupportedEncodingException, NoSuchAlgorithmException { 48 | String email = registerDTO.getEmail(); 49 | if (!registerDTO.getPassword1().equals(registerDTO.getPassword2())) { 50 | throw new BusinessException(BusinessCodeEnum.PASSWORD_NO_EQUAL); 51 | } 52 | String emailCodeKey = RedisConstant.EMAIL_CODE_KEY + email; 53 | String emailCode = (String) redisTemplate.opsForValue().get(emailCodeKey); 54 | if (!registerDTO.getEmailCode().equalsIgnoreCase(emailCode)) { 55 | throw new BusinessException(BusinessCodeEnum.CODE_EMAIL_ERROR); 56 | } 57 | LinkUser userPO = userStore.getByEmail(email); 58 | if (ObjectUtils.isNotEmpty(userPO)) { 59 | throw new BusinessException(BusinessCodeEnum.ACCOUNT_REPEAT); 60 | } 61 | String salt = "$1$" + RandomUtil.generate(8, 3); 62 | String password = EncryptUtil.md5(registerDTO.getPassword1() + salt); 63 | long snowFlakeId = SnowFlakeUtil.nextId(); 64 | userPO = UserAdapter.buildUserPO(snowFlakeId, "用户" + snowFlakeId, "", email, password, salt); 65 | userStore.saveUser(userPO); 66 | } 67 | 68 | @Override 69 | public Map login(LoginDTO loginDTO) throws UnsupportedEncodingException, NoSuchAlgorithmException { 70 | String captchaKey = assistService.getCaptchaKey(); 71 | String captchaCode = (String) redisTemplate.opsForValue().get(captchaKey); 72 | if (!loginDTO.getCaptchaCode().equalsIgnoreCase(captchaCode)) { 73 | throw new BusinessException(BusinessCodeEnum.CODE_CAPTCHA_ERROR); 74 | } 75 | LinkUser userPO = userStore.getByEmail(loginDTO.getEmail()); 76 | if (ObjectUtils.isEmpty(userPO)) { 77 | throw new BusinessException(BusinessCodeEnum.ACCOUNT_UNREGISTER); 78 | } 79 | String token = JwtUtil.generate(userPO.getAccountId(), userPO.getEmail(), userPO.getNickName(), userPO.getAvatar()); 80 | Map resultMap = new HashMap<>(); 81 | resultMap.put("token", token); 82 | resultMap.put("user", UserAdapter.buildUserVO(userPO)); 83 | return resultMap; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/service/impl/UserInfoServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.minilink.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; 4 | import com.minilink.adapter.UserAdapter; 5 | import com.minilink.pojo.po.LinkUser; 6 | import com.minilink.pojo.vo.UserVO; 7 | import com.minilink.service.UserInfoService; 8 | import com.minilink.store.LinkUserStore; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | /** 14 | *

15 | * 服务实现类 16 | *

17 | * 18 | * @author 徐志斌 19 | * @since 2024-12-06 20 | */ 21 | @Slf4j 22 | @Service 23 | public class UserInfoServiceImpl implements UserInfoService { 24 | @Autowired 25 | private LinkUserStore userStore; 26 | 27 | @Override 28 | public UserVO getUserInfo(Long accountId) { 29 | LinkUser userPO = userStore.getByAccountId(accountId); 30 | if (ObjectUtils.isEmpty(userPO)) { 31 | return null; 32 | } 33 | return UserAdapter.buildUserVO(userPO); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/store/LinkUserStore.java: -------------------------------------------------------------------------------- 1 | package com.minilink.store; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.minilink.pojo.po.LinkUser; 5 | 6 | /** 7 | *

8 | * 服务类 9 | *

10 | * 11 | * @author 徐志斌 12 | * @since 2024-12-06 13 | */ 14 | public interface LinkUserStore extends IService { 15 | /** 16 | * 根据 email 查询账户信息 17 | * 18 | * @param email 邮箱 19 | * @return 账户信息 20 | */ 21 | LinkUser getByEmail(String email); 22 | 23 | /** 24 | * 保存账户信息 25 | * 26 | * @param userPO 用户信息 27 | * @return 执行结果 28 | */ 29 | Boolean saveUser(LinkUser userPO); 30 | 31 | /** 32 | * 根据 account_id 查询用户信息 33 | * 34 | * @param accountId 账号 35 | * @return 用户信息 36 | */ 37 | LinkUser getByAccountId(Long accountId); 38 | } 39 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/store/impl/LinkUserStoreImpl.java: -------------------------------------------------------------------------------- 1 | package com.minilink.store.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.minilink.constant.RedisConstant; 5 | import com.minilink.mapper.LinkUserMapper; 6 | import com.minilink.pojo.po.LinkUser; 7 | import com.minilink.store.LinkUserStore; 8 | import org.springframework.cache.annotation.Cacheable; 9 | import org.springframework.stereotype.Service; 10 | 11 | /** 12 | *

13 | * 服务实现类 14 | *

15 | * 16 | * @author 徐志斌 17 | * @since 2024-12-06 18 | */ 19 | @Service 20 | public class LinkUserStoreImpl extends ServiceImpl implements LinkUserStore { 21 | @Override 22 | @Cacheable(value = RedisConstant.CACHE_LINK_USER, key = "#p0", unless = "#result == null") 23 | public LinkUser getByEmail(String email) { 24 | return this.lambdaQuery() 25 | .eq(LinkUser::getEmail, email) 26 | .one(); 27 | } 28 | 29 | @Override 30 | public Boolean saveUser(LinkUser userPO) { 31 | return this.save(userPO); 32 | } 33 | 34 | @Override 35 | @Cacheable(value = RedisConstant.CACHE_LINK_USER, key = "#p0", unless = "#result == null") 36 | public LinkUser getByAccountId(Long accountId) { 37 | return this.lambdaQuery() 38 | .eq(LinkUser::getAccountId, accountId) 39 | .one(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/strategy/email/AbstractEmailStrategy.java: -------------------------------------------------------------------------------- 1 | package com.minilink.strategy.email; 2 | 3 | 4 | import com.minilink.enums.EmailEnum; 5 | import com.minilink.pojo.entity.EmailParamEntity; 6 | import jakarta.annotation.PostConstruct; 7 | 8 | /** 9 | * @Author: 徐志斌 10 | * @CreateTime: 2023-11-07 10:50 11 | * @Description: 发送邮件-策略配置 12 | * @Version: 1.0 13 | */ 14 | public abstract class AbstractEmailStrategy { 15 | @PostConstruct 16 | private void initStrategyHandler() { 17 | EmailStrategyFactory.register(this.getEmailEnum().getCode(), this); 18 | } 19 | 20 | /** 21 | * 获取当前邮件枚举 22 | * 23 | * @return 邮件枚举 24 | */ 25 | protected abstract EmailEnum getEmailEnum(); 26 | 27 | /** 28 | * 发送邮件 29 | * 30 | * @param emailParam 发送邮件参数 31 | */ 32 | public abstract void sendEmail(EmailParamEntity emailParam); 33 | } 34 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/strategy/email/EmailStrategyFactory.java: -------------------------------------------------------------------------------- 1 | package com.minilink.strategy.email; 2 | 3 | import com.minilink.enums.BusinessCodeEnum; 4 | import com.minilink.exception.BusinessException; 5 | import org.apache.commons.lang3.ObjectUtils; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Author: 徐志斌 12 | * @CreateTime: 2023-09-14 14:32 13 | * @Description: 发送邮件-策略工厂 14 | * @Version: 1.0 15 | */ 16 | public class EmailStrategyFactory { 17 | private static final Map STRATEGY_MAP = new HashMap<>(); 18 | 19 | public static void register(Integer code, AbstractEmailStrategy emailStrategy) { 20 | STRATEGY_MAP.put(code, emailStrategy); 21 | } 22 | 23 | public static AbstractEmailStrategy getStrategyHandler(Integer code) { 24 | AbstractEmailStrategy strategyHandler = STRATEGY_MAP.get(code); 25 | if (ObjectUtils.isEmpty(strategyHandler)) { 26 | throw new BusinessException(BusinessCodeEnum.FAIL); 27 | } 28 | return strategyHandler; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/strategy/email/handler/RegisterEmailHandler.java: -------------------------------------------------------------------------------- 1 | package com.minilink.strategy.email.handler; 2 | 3 | import com.minilink.constant.RedisConstant; 4 | import com.minilink.enums.EmailEnum; 5 | import com.minilink.pojo.entity.EmailParamEntity; 6 | import com.minilink.strategy.email.AbstractEmailStrategy; 7 | import com.minilink.util.EmailUtil; 8 | import com.minilink.util.RandomUtil; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.data.redis.core.RedisTemplate; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.concurrent.TimeUnit; 14 | 15 | /** 16 | * @Author: 徐志斌 17 | * @CreateTime: 2024-12-23 10:16 18 | * @Description: 注册邮件 19 | * @Version: 1.0 20 | */ 21 | @Component 22 | public class RegisterEmailHandler extends AbstractEmailStrategy { 23 | @Autowired 24 | private EmailUtil emailUtil; 25 | @Autowired 26 | private RedisTemplate redisTemplate; 27 | 28 | @Override 29 | protected EmailEnum getEmailEnum() { 30 | return EmailEnum.REGISTER; 31 | } 32 | 33 | @Override 34 | public void sendEmail(EmailParamEntity emailParam) { 35 | String email = emailParam.getEmail(); 36 | String emailCode = RandomUtil.generate(4, 1); 37 | String subject = "Mini Link注册账号"; 38 | String content = "您正在使用 Mini Link 注册账号功能,您的邮件验证码为:%s(有效期3分钟),请妥善保管,不要泄露给他人!"; 39 | 40 | String emailKey = RedisConstant.EMAIL_CODE_KEY + email; 41 | redisTemplate.opsForValue().set(emailKey, emailCode, 3, TimeUnit.MINUTES); 42 | emailUtil.sendTextMail(email, subject, String.format(content, emailCode)); 43 | String emailCheckKey = RedisConstant.EMAIL_CHECK_KEY + email; 44 | redisTemplate.opsForValue().set(emailCheckKey, "Email Repeat Send Check", 60, TimeUnit.SECONDS); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /mini-link-user/src/main/java/com/minilink/util/EmailUtil.java: -------------------------------------------------------------------------------- 1 | package com.minilink.util; 2 | 3 | import com.minilink.config.MyThreadPoolExecutor; 4 | import jakarta.annotation.Resource; 5 | import jakarta.mail.MessagingException; 6 | import jakarta.mail.internet.InternetAddress; 7 | import jakarta.mail.internet.MimeMessage; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.core.io.FileSystemResource; 10 | import org.springframework.mail.SimpleMailMessage; 11 | import org.springframework.mail.javamail.JavaMailSender; 12 | import org.springframework.mail.javamail.MimeMessageHelper; 13 | import org.springframework.scheduling.annotation.Async; 14 | import org.springframework.stereotype.Component; 15 | 16 | import java.io.File; 17 | 18 | /** 19 | * @author 徐志斌 20 | * @description: 发送邮件工具类 21 | * @date 2023/12/26 16:52 22 | * @Version: 1.0 23 | */ 24 | 25 | @Component 26 | public class EmailUtil { 27 | @Resource 28 | private JavaMailSender mailSender; 29 | @Value("${spring.mail.username}") 30 | private String fromEmail; 31 | 32 | /** 33 | * 文本邮件 34 | * 35 | * @param toEmail 收件人 36 | * @param subject 主题 37 | * @param content 内容 38 | */ 39 | @Async(MyThreadPoolExecutor.THREAD_POOL_NAME) 40 | public void sendTextMail(String toEmail, String subject, String content) { 41 | SimpleMailMessage message = new SimpleMailMessage(); 42 | message.setFrom(fromEmail); 43 | message.setTo(toEmail); 44 | message.setSubject(subject); 45 | message.setText(content); 46 | mailSender.send(message); 47 | } 48 | 49 | /** 50 | * HTML邮件 51 | * 52 | * @param toEmail 收件人,多个时参数形式 :"xxx@xxx.com,xxx@xxx.com,xxx@xxx.com" 53 | * @param subject 主题 54 | * @param content 内容 55 | */ 56 | @Async(MyThreadPoolExecutor.THREAD_POOL_NAME) 57 | public void sendHtmlMail(String toEmail, String subject, String content) throws MessagingException { 58 | MimeMessage message = mailSender.createMimeMessage(); 59 | MimeMessageHelper messageHelper = new MimeMessageHelper(message, true); 60 | messageHelper.setFrom(fromEmail); 61 | InternetAddress[] internetAddressTo = InternetAddress.parse(toEmail); 62 | messageHelper.setTo(internetAddressTo); 63 | message.setSubject(subject); 64 | messageHelper.setText(content, true); 65 | mailSender.send(message); 66 | } 67 | 68 | /** 69 | * 带附件的邮件 70 | * 71 | * @param to 收件人 72 | * @param subject 主题 73 | * @param content 内容 74 | * @param filePath 附件 75 | */ 76 | @Async(MyThreadPoolExecutor.THREAD_POOL_NAME) 77 | public void sendAttachmentsMail(String to, String subject, String content, String filePath) throws MessagingException { 78 | MimeMessage message = mailSender.createMimeMessage(); 79 | MimeMessageHelper helper = new MimeMessageHelper(message, true); 80 | helper.setFrom(fromEmail); 81 | helper.setTo(to); 82 | helper.setSubject(subject); 83 | helper.setText(content, true); 84 | FileSystemResource file = new FileSystemResource(new File(filePath)); 85 | String fileName = filePath.substring(filePath.lastIndexOf(File.separator)); 86 | helper.addAttachment(fileName, file); 87 | mailSender.send(message); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /mini-link-user/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | driver-class-name: com.mysql.cj.jdbc.Driver 4 | url: jdbc:mysql://localhost:3306/mini_link_user?serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true 5 | username: root 6 | password: Pxy161122 7 | data: 8 | redis: 9 | host: localhost 10 | port: 6379 11 | database: 0 12 | password: Pxy123456 13 | timeout: 3000 14 | connectTimeout: 5000 15 | lettuce: 16 | pool: 17 | min-idle: 100 18 | max-idle: 100 19 | max-active: 100 20 | max-wait: 20000 21 | mail: 22 | host: smtp.qq.com 23 | port: 465 24 | username: 1262254123@qq.com 25 | password: ocebnufztunlbagc 26 | default-encoding: UTF-8 27 | properties: 28 | mail: 29 | debug: true 30 | smtp: 31 | socketFactory: 32 | class: javax.net.ssl.SSLSocketFactory 33 | 34 | storage: 35 | minio: 36 | endpoint: http://localhost:9000 37 | access-key: admin 38 | secret-key: 12345678 39 | bucketName: 40 | avatar: avatar-bucket 41 | qrcode: qrcode-bucket 42 | 43 | -------------------------------------------------------------------------------- /mini-link-user/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: dev 4 | application: 5 | name: mini-link-user 6 | server: 7 | port: 8001 8 | servlet: 9 | context-path: /user -------------------------------------------------------------------------------- /mini-link-user/src/main/resources/deploy/deployment-user.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mini-link-user-deployment 5 | namespace: mini-link-namespace 6 | labels: 7 | app: mini-link-user-label 8 | spec: 9 | replicas: 3 10 | selector: 11 | matchLabels: 12 | app: mini-link-user-label 13 | template: 14 | metadata: 15 | labels: 16 | app: mini-link-user-label 17 | spec: 18 | containers: 19 | - name: mini-link-user 20 | image: 镜像仓库IP:端口/minilink/mini-link-user:1.0 21 | imagePullPolicy: Always 22 | ports: 23 | - containerPort: 8001 24 | resources: 25 | requests: 26 | memory: 300Mi 27 | cpu: 200m 28 | limits: 29 | memory: 500Mi 30 | cpu: 400m 31 | readinessProbe: 32 | tcpSocket: 33 | port: 8001 34 | initialDelaySeconds: 60 35 | periodSeconds: 10 36 | livenessProbe: 37 | tcpSocket: 38 | port: 8001 39 | initialDelaySeconds: 60 40 | periodSeconds: 10 -------------------------------------------------------------------------------- /mini-link-user/src/main/resources/deploy/mini_link_user.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : localhost 5 | Source Server Type : MySQL 6 | Source Server Version : 101102 (10.11.2-MariaDB) 7 | Source Host : localhost:3306 8 | Source Schema : mini_link 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 101102 (10.11.2-MariaDB) 12 | File Encoding : 65001 13 | 14 | Date: 13/12/2024 17:27:54 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for mini_link_user 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `mini_link_user`; 24 | CREATE TABLE `mini_link_user` ( 25 | `id` bigint NOT NULL COMMENT '主键', 26 | `account_id` bigint NULL DEFAULT NULL COMMENT '账号id', 27 | `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '邮箱', 28 | `avatar` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '头像', 29 | `nick_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '昵称', 30 | `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '密码', 31 | `salt` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '盐', 32 | `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 33 | `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间', 34 | `deleted` tinyint(1) NULL DEFAULT NULL COMMENT '删除标识', 35 | PRIMARY KEY (`id`) USING BTREE, 36 | UNIQUE INDEX `uk_email`(`email` ASC) USING BTREE 37 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic; 38 | 39 | SET FOREIGN_KEY_CHECKS = 1; 40 | -------------------------------------------------------------------------------- /mini-link-user/src/main/resources/deploy/service-user.yml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: mini-link-user-service 5 | namespace: mini-link-namespace 6 | labels: 7 | app: mini-link-user-label 8 | spec: 9 | selector: 10 | app: mini-link-user-label 11 | type: ClusterIP 12 | ports: 13 | - protocol: TCP 14 | port: 8001 15 | targetPort: 8001 16 | -------------------------------------------------------------------------------- /mini-link-user/src/main/resources/mapper/LinkUserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | id, email, avatar, nick_name, password, salt, create_time, update_time, deleted 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | org.springframework.boot 6 | spring-boot-starter-parent 7 | 3.2.0 8 | 9 | 10 | 11 | com.minilink 12 | mini-link 13 | 0.0.1-SNAPSHOT 14 | mini-link 15 | 4.0.0 16 | pom 17 | 18 | 19 | mini-link-common 20 | mini-link-core 21 | mini-link-data 22 | mini-link-flink 23 | mini-link-gateway 24 | mini-link-user 25 | 26 | 27 | 28 | 17 29 | 3.2.0 30 | 2023.0.1 31 | 2023.0.1.0 32 | 8.0.16 33 | 3.5.9 34 | 1.1.0 35 | 2.1.0 36 | 5.5.1 37 | 5.8.23 38 | 0.9.0 39 | 2.3.0 40 | 1.14.3 41 | 8.5.2 42 | 3.1.0 43 | 4.9.1 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-web 52 | ${spring-boot.version} 53 | 54 | 55 | 56 | org.springframework.cloud 57 | spring-cloud-dependencies 58 | ${spring-cloud.version} 59 | pom 60 | import 61 | 62 | 63 | 64 | com.alibaba.cloud 65 | spring-cloud-alibaba-dependencies 66 | ${spring-cloud-alibaba.version} 67 | pom 68 | import 69 | 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-starter-data-redis 74 | ${spring-boot.version} 75 | 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-starter-validation 80 | ${spring-boot.version} 81 | 82 | 83 | 84 | org.springframework.boot 85 | spring-boot-starter-aop 86 | ${spring-boot.version} 87 | 88 | 89 | 90 | org.springframework.boot 91 | spring-boot-starter-mail 92 | ${spring-boot.version} 93 | 94 | 95 | 96 | mysql 97 | mysql-connector-java 98 | ${mysql.version} 99 | 100 | 101 | 102 | com.baomidou 103 | mybatis-plus-spring-boot3-starter 104 | ${mybatis-plus.version} 105 | 106 | 107 | com.baomidou 108 | mybatis-plus-generator 109 | ${mybatis-plus.version} 110 | 111 | 112 | com.baomidou 113 | mybatis-plus-jsqlparser 114 | ${mybatis-plus.version} 115 | 116 | 117 | 118 | org.springframework.kafka 119 | spring-kafka 120 | ${kafka.version} 121 | 122 | 123 | 124 | com.baomidou 125 | kaptcha-spring-boot-starter 126 | ${captcha.version} 127 | 128 | 129 | 130 | org.springdoc 131 | springdoc-openapi-starter-webmvc-ui 132 | ${springdoc.version} 133 | 134 | 135 | 136 | io.jsonwebtoken 137 | jjwt 138 | ${jwt.version} 139 | 140 | 141 | 142 | org.apache.shardingsphere 143 | shardingsphere-jdbc 144 | ${sharding-jdbc.version} 145 | 146 | 147 | 148 | cn.hutool 149 | hutool-all 150 | ${hutool.version} 151 | 152 | 153 | 154 | javax.xml.bind 155 | jaxb-api 156 | ${jaxb.api.version} 157 | 158 | 159 | 160 | org.jsoup 161 | jsoup 162 | ${jsoup.version} 163 | 164 | 165 | 166 | io.minio 167 | minio 168 | ${minio.version} 169 | 170 | 171 | 172 | cn.hutool 173 | hutool-all 174 | ${hutool.version} 175 | 176 | 177 | 178 | com.squareup.okhttp3 179 | okhttp 180 | ${okhttp.version} 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | org.springframework.boot 189 | spring-boot-maven-plugin 190 | 191 | 192 | 193 | 194 | --------------------------------------------------------------------------------