feat: 移植工程

This commit is contained in:
2025-06-10 09:54:52 +08:00
commit a2de21b092
277 changed files with 11116 additions and 0 deletions

6
blog-service/Dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM docker-0.unsee.tech/openjdk:11-jdk-slim
ARG JAR_FILE=target/*.jar
COPY $JAR_FILE app.jar
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' > /etc/timezone
ENTRYPOINT ["java","-jar","/app.jar"]

1
blog-service/pom.xml Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?>

View File

@@ -0,0 +1,13 @@
package com.cxx.blog;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = {"com.cxx.common.mapper", "com.cxx.blog.dao"})
public class BlogServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BlogServiceApplication.class, args);
}
}

View File

@@ -0,0 +1,71 @@
package com.cxx.blog.config;
import com.cxx.blog.service.BlogService;
import com.cxx.common.entity.BlogVisit;
import com.cxx.blog.util.IpUtils;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* 利用AOP监控博客访问
* 1. @Before前置通知在方法执行之前执行
* 2. @After后置通知在方法执行之后执行
* 3. @AfterRunning返回通知在方法返回结果之后执行
* 4. @AfterThrowing异常通知在方法抛出异常之后执行
* 5. @Around环绕通知围绕着方法执行
*/
@Component
@Aspect
@Slf4j
public class BlogVisitMonitor {
@Resource
private BlogService blogService;
/**
* 定义切面
* 指定需要统计的包
*/
@Pointcut("execution(* com.cxx.blog.controller.BlogController.*(..))")
public void pointCut() {
}
/**
* 只有正常返回才会执行此方法
* 如果程序执行失败,则不执行此方法
*/
@AfterReturning(returning = "returnVal", pointcut = "pointCut()")
public void doAfterReturning(JoinPoint joinPoint, Object returnVal) {
// 获取接收到的HTTP请求信息
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String agent = request.getHeader("User-Agent");
UserAgent userAgent = UserAgent.parseUserAgentString(agent);
OperatingSystem os = userAgent.getOperatingSystem();
Browser browser = userAgent.getBrowser();
String uri = request.getRequestURI();
BlogVisit blogVisit = new BlogVisit();
blogVisit.setIp(IpUtils.getIpAddress(request));
blogVisit.setOs(os.getName());
blogVisit.setBrowser(browser.getName() + "-" + userAgent.getBrowserVersion());
blogVisit.setUri(uri);
blogVisit.setBlogId(0L);
if (blogVisit.getUri().contains("content")) {
blogVisit.setBlogId(Long.valueOf(uri.split("/")[3]));
}
blogService.addBlogVisit(blogVisit);
}
}

View File

@@ -0,0 +1,62 @@
package com.cxx.blog.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.cxx.blog.service.BlogService;
import com.cxx.common.ReadView;
import com.cxx.common.dto.blog.BlogCategoryDto;
import com.cxx.common.dto.blog.BlogDto;
import com.cxx.common.dto.blog.BlogLatestDto;
import com.cxx.common.dto.blog.BlogStatsDto;
import com.cxx.common.vo.blog.BlogQueryVo;
import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/blog")
@Tag(name = "查看博客")
public class BlogController {
@Resource
private BlogService blogService;
@Operation(summary = "分页查询博客")
@GetMapping("/page")
public IPage<BlogDto> queryBlogByPage(@RequestParam("currentPage") Integer currentPage,
@RequestParam("pageSize") Integer pageSize) {
return blogService.queryBlogByPage(currentPage, pageSize);
}
@Operation(summary = "条件查询博客")
@GetMapping("/condition")
public @JsonView(ReadView.class) List<BlogDto> queryBlogByCondition(BlogQueryVo query) {
return blogService.queryBlogByCondition(query);
}
@Operation(summary = "查询博客内容")
@GetMapping("/content/{id}")
public @JsonView(ReadView.class) BlogDto queryBlogById(@PathVariable("id") long id) {
return blogService.queryBlogById(id);
}
@Operation(summary = "查询博客分类")
@GetMapping("/category")
public List<BlogCategoryDto> queryBlogCategory() {
return blogService.queryBlogCategory();
}
@Operation(summary = "查询博客统计信息")
@GetMapping("/stats")
public BlogStatsDto queryBlogStats() {
return blogService.queryBlogStats();
}
@Operation(summary = "查询近期博客")
@GetMapping("/latest")
public List<BlogLatestDto> queryLatestBlog() {
return blogService.queryLatestBlog();
}
}

View File

@@ -0,0 +1,25 @@
package com.cxx.blog.dao;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.cxx.common.dto.blog.BlogCategoryDto;
import com.cxx.common.dto.blog.BlogDto;
import com.cxx.common.dto.blog.BlogLatestDto;
import com.cxx.common.entity.Blog;
import com.cxx.common.vo.blog.BlogQueryVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface BlogDao {
IPage<BlogDto> queryBlogByPage(IPage<Blog> page);
List<BlogDto> queryBlogByCondition(@Param("query") BlogQueryVo query);
BlogDto queryBlogById(@Param("id") Long id);
List<BlogCategoryDto> queryBlogCategory();
List<BlogLatestDto> queryLatestBlog();
}

View File

@@ -0,0 +1,28 @@
package com.cxx.blog.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.cxx.common.dto.blog.BlogCategoryDto;
import com.cxx.common.dto.blog.BlogDto;
import com.cxx.common.dto.blog.BlogLatestDto;
import com.cxx.common.dto.blog.BlogStatsDto;
import com.cxx.common.entity.BlogVisit;
import com.cxx.common.vo.blog.BlogQueryVo;
import java.util.List;
public interface BlogService {
IPage<BlogDto> queryBlogByPage(Integer currentPage, Integer pageSize);
List<BlogDto> queryBlogByCondition(BlogQueryVo query);
BlogDto queryBlogById(Long id);
List<BlogCategoryDto> queryBlogCategory();
BlogStatsDto queryBlogStats();
List<BlogLatestDto> queryLatestBlog();
void addBlogVisit(BlogVisit blogVisit);
}

View File

@@ -0,0 +1,73 @@
package com.cxx.blog.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cxx.blog.dao.BlogDao;
import com.cxx.blog.service.BlogService;
import com.cxx.common.dto.blog.BlogCategoryDto;
import com.cxx.common.dto.blog.BlogDto;
import com.cxx.common.dto.blog.BlogLatestDto;
import com.cxx.common.dto.blog.BlogStatsDto;
import com.cxx.common.entity.BlogVisit;
import com.cxx.common.mapper.BlogCategoryMapper;
import com.cxx.common.mapper.BlogMapper;
import com.cxx.common.mapper.BlogVisitMapper;
import com.cxx.common.vo.blog.BlogQueryVo;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BlogServiceImpl implements BlogService {
@Resource
private BlogDao blogDao;
@Resource
private BlogMapper blogMapper;
@Resource
private BlogCategoryMapper categoryMapper;
@Resource
private BlogVisitMapper visitMapper;
@Override
public IPage<BlogDto> queryBlogByPage(Integer currentPage, Integer pageSize) {
return blogDao.queryBlogByPage(new Page<>(currentPage, pageSize));
}
@Override
public List<BlogDto> queryBlogByCondition(BlogQueryVo query) {
return blogDao.queryBlogByCondition(query);
}
@Override
public BlogDto queryBlogById(Long id) {
return blogDao.queryBlogById(id);
}
@Override
public List<BlogCategoryDto> queryBlogCategory() {
return blogDao.queryBlogCategory();
}
@Override
public BlogStatsDto queryBlogStats() {
BlogStatsDto blogStats = new BlogStatsDto();
blogStats.setBlogCount(blogMapper.selectCount(null));
blogStats.setCategoryCount(categoryMapper.selectCount(null));
return blogStats;
}
@Override
public List<BlogLatestDto> queryLatestBlog() {
return blogDao.queryLatestBlog();
}
@Override
public void addBlogVisit(BlogVisit blogVisit) {
visitMapper.insert(blogVisit);
}
}

View File

@@ -0,0 +1,36 @@
package com.cxx.blog.util;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class BlogUtils {
public static File multipartFileToFile(MultipartFile multipartFile) {
if (multipartFile.getOriginalFilename() == null) {
return null;
}
File file = new File(multipartFile.getOriginalFilename());
try {
InputStream ins = multipartFile.getInputStream();
OutputStream os = new FileOutputStream(file);
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
ins.close();
} catch (Exception e) {
e.printStackTrace();
}
return file;
}
}

View File

@@ -0,0 +1,59 @@
package com.cxx.blog.util;
import jakarta.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IpUtils {
private static final String UNKNOWN = "unknown";
private static final String LOCALHOST_IP = "127.0.0.1";
// 客户端与服务器同为一台机器,获取的 ip 有时候是 ipv6 格式
private static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
private static final String SEPARATOR = ",";
/**
* 根据 HttpServletRequest 获取 IP
* @param request 请求
* @return ip
*/
public static String getIpAddress(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
if (LOCALHOST_IP.equalsIgnoreCase(ip) || LOCALHOST_IPV6.equalsIgnoreCase(ip)) {
// 根据网卡取本机配置的 IP
InetAddress iNet = null;
try {
iNet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
if (iNet != null)
ip = iNet.getHostAddress();
}
}
// 对于通过多个代理的情况,分割出第一个 IP
if (ip != null && ip.length() > 15) {
if (ip.indexOf(SEPARATOR) > 0) {
ip = ip.substring(0, ip.indexOf(SEPARATOR));
}
}
return LOCALHOST_IPV6.equals(ip) ? LOCALHOST_IP : ip;
}
}

View File

@@ -0,0 +1,11 @@
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/sweet_hut?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
logging:
level:
root: info
config:

View File

@@ -0,0 +1,13 @@
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql:3306/sweet_hut?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: docker
password: 19940822Cxx
logging:
fluentd:
host: fluent-bit
port: 24224
level:
root: info

View File

@@ -0,0 +1,12 @@
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/sweet_hut?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: cxx
password: 19940822Cxx@1213
logging:
level:
root: info
config:
classpath:logback-spring-drop.xml

View File

@@ -0,0 +1,20 @@
server:
port: 8082
spring:
application:
name: @project.artifactId@
version: @project.version@
profiles:
active: dev
# mybatis plus mapper路径
mybatis-plus:
# mybatis plus
mapper-locations: classpath*:/mapper/**/*.xml
configuration:
log-impl:
org.apache.ibatis.logging.stdout.StdOutImpl
web-starter:
base-package: com.cxx.blog

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds">
<contextName>logback</contextName>
<property name="log.path" value="logs/blogService"/>
<!--控制台日志格式:彩色日志-->
<!-- magenta:洋红 -->
<!-- boldMagenta:粗红-->
<!-- cyan:青色 -->
<!-- white:白色 -->
<!-- magenta:洋红 -->
<property name="CONSOLE_LOG_PATTERN"
value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>
<!--文件日志格式-->
<property name="FILE_LOG_PATTERN"
value="%date{yyyy-MM-dd HH:mm:ss} |%-5level |%thread |%file:%line |%logger |%msg%n"/>
<!--编码-->
<property name="ENCODING"
value="UTF-8"/>
<!--输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!--日志级别-->
<level>DEBUG</level>
</filter>
<encoder>
<!--日志格式-->
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!--日志字符集-->
<charset>${ENCODING}</charset>
</encoder>
</appender>
<!--输出到文件-->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志过滤器此日志文件只记录INFO级别的-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_info.log</file>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>${ENCODING}</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>500MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
</appender>
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志过滤器此日志文件只记录WARN级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_warn.log</file>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>${ENCODING}</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志过滤器此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_error.log</file>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>${ENCODING}</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</configuration>

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cxx.blog.dao.BlogDao">
<select id="queryBlogByPage" resultType="com.cxx.common.dto.blog.BlogDto">
SELECT a.id AS id,
a.title AS title,
a.top_value AS topValue,
a.is_great AS isGreat,
b.NAME AS category,
a.summary AS summary,
a.word_count AS wordCount,
a.read_duration AS readDuration,
COUNT(c.id) AS visitCount,
a.create_time AS createTime,
a.update_time AS updateTime
FROM blog a
LEFT JOIN blog_category b ON b.id = a.category_id
LEFT JOIN blog_visit c ON c.blog_id = a.id
GROUP BY a.id
ORDER BY a.top_value DESC,
a.update_time DESC
</select>
<select id="queryBlogByCondition" resultType="com.cxx.common.dto.blog.BlogDto">
SELECT a.id AS id,
a.title AS title,
a.top_value AS topValue,
a.is_great AS isGreat,
b.name AS category,
a.summary AS summary,
c.content AS content,
a.word_count AS wordCount,
a.read_duration AS readDuration,
a.create_time AS createTime,
a.update_time AS updateTime
FROM blog a
LEFT JOIN blog_category b on a.category_id = b.id
LEFT JOIN blog_content c on a.content_id = c.id
<where>
<if test="query.category != null and query.category != ''">
b.name = #{query.category}
</if>
<if test="query.title != null and query.title != ''">
AND a.title LIKE CONCAT('%',#{query.title},'%')
</if>
<if test="query.year != null">
AND YEAR(a.create_time) = #{query.year}
</if>
</where>
ORDER BY a.update_time DESC
</select>
<select id="queryBlogById" resultType="com.cxx.common.dto.blog.BlogDto">
SELECT a.id AS id,
a.title AS title,
a.top_value AS topValue,
a.is_great AS isGreat,
b.NAME AS category,
a.summary AS summary,
c.content AS content,
a.word_count AS wordCount,
a.read_duration AS readDuration,
COUNT(d.id) AS visitCount,
a.create_time AS createTime,
a.update_time AS updateTime
FROM blog a
LEFT JOIN blog_category b ON b.id = a.category_id
LEFT JOIN blog_content c ON c.id = a.content_id
LEFT JOIN blog_visit d ON d.blog_id = a.id
WHERE a.id = #{id}
LIMIT 1
</select>
<select id="queryBlogCategory" resultType="com.cxx.common.dto.blog.BlogCategoryDto">
SELECT b.name AS name,
count(b.name) AS count
FROM blog a
LEFT JOIN blog_category b
ON a.category_id = b.id
GROUP BY b.name
</select>
<select id="queryLatestBlog" resultType="com.cxx.common.dto.blog.BlogLatestDto">
SELECT id AS id,
title AS title
FROM blog
ORDER BY update_time DESC
LIMIT 5
</select>
</mapper>

View File

@@ -0,0 +1,18 @@
package com.cxx.blog;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @Author: Cxx
* @Date: 2024/9/22 0:17
* @Description:
*/
@SpringBootTest
public class BlogServiceTest {
@Test
public void test() {
}
}