后端:
依赖准备
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.Lyu</groupId>
<artifactId>LyuBlog</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>LyuBlog</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- spring web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- spring retry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- mybatis分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- Markdown 转 HTML -->
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.18.1</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark-ext-heading-anchor</artifactId>
<version>0.18.1</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark-ext-gfm-tables</artifactId>
<version>0.18.1</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark-ext-gfm-strikethrough</artifactId>
<version>0.18.1</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark-ext-task-list-items</artifactId>
<version>0.18.1</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- thymeleaf模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- ip2region -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.6.5</version>
</dependency>
<!-- 解析客户端操作系统、浏览器 -->
<dependency>
<groupId>nl.basjes.parse.useragent</groupId>
<artifactId>yauaa</artifactId>
<version>7.11.0</version>
</dependency>
<!-- configuration processor -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 又拍云存储SDK -->
<dependency>
<groupId>com.upyun</groupId>
<artifactId>java-sdk</artifactId>
<version>4.2.3</version>
</dependency>
<!-- hutool-crypto -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>5.8.11</version>
</dependency>
<!-- devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
需要的各种类、接口:
entity:
About(关于我实体类):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class About {
private Long id;
private String nameEn;
private String nameZh;
private String value;
}
Blog(博客文章):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class Blog {
private Long id;
private String title;//文章标题
private String firstPicture;//文章首图,用于随机文章展示
private String content;//文章正文
private String description;//描述
private Boolean published;//公开或私密
private Boolean recommend;//推荐开关
private Boolean appreciation;//赞赏开关
private Boolean commentEnabled;//评论开关
private Boolean top;//是否置顶
private Date createTime;//创建时间
private Date updateTime;//更新时间
private Integer views;//浏览次数
private Integer words;//文章字数
private Integer readTime;//阅读时长(分钟)
private String password;//密码保护
private User user;//文章作者(因为是个人博客,也可以不加作者字段,暂且加上)
private Category category;//文章分类
private List<Tag> tags = new ArrayList<>();//文章标签
}
Category(博客分类):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class Category {
private Long id;
private String name;//分类名称
private List<Blog> blogs = new ArrayList<>();//该分类下的博客文章
}
CityVisitor(城市访客数量):
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
public class CityVisitor {
private String city;//城市名称
private Integer uv;//独立访客数量
}
Comment(评论):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class Comment {
private Long id;
private String nickname;//昵称
private String email;//邮箱
private String content;//评论内容
private String avatar;//头像(图片路径)
private Date createTime;//评论时间
private String website;//个人网站
private String ip;//评论者ip地址
private Boolean published;//公开或回收站
private Boolean adminComment;//博主回复
private Integer page;//0普通文章,1关于我页面
private Boolean notice;//接收邮件提醒
private Long parentCommentId;//父评论id
private String qq;//如果评论昵称为QQ号,则将昵称和头像置为QQ昵称和QQ头像,并将此字段置为QQ号备份
private BlogIdAndTitle blog;//所属的文章
private List<Comment> replyComments = new ArrayList<>();//回复该评论的评论
}
ExceptionLog(异常日志):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class ExceptionLog {
private Long id;
private String uri;//请求接口
private String method;//请求方式
private String param;//请求参数
private String description;//操作描述
private String error;//异常信息
private String ip;//ip
private String ipSource;//ip来源
private String os;//操作系统
private String browser;//浏览器
private Date createTime;//操作时间
private String userAgent;
//构造函数
public ExceptionLog(String uri, String method, String description, String error, String ip, String userAgent) {
this.uri = uri;
this.method = method;
this.description = description;
this.error = error;
this.ip = ip;
this.createTime = new Date();
this.userAgent = userAgent;
}
}
Friend(友链):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class Friend {
private Long id;
private String nickname;//昵称
private String description;//描述
private String website;//站点
private String avatar;//头像
private Boolean published;//公开或隐藏
private Integer views;//浏览次数
private Date createTime;//创建时间
}
LoginLog(登录日志):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class LoginLog {
private Long id;
private String username;//用户名称
private String ip;//ip
private String ipSource;//ip来源
private String os;//操作系统
private String browser;//浏览器
private Boolean status;//登录状态
private String description;//操作信息
private Date createTime;//操作时间
private String userAgent;
public LoginLog(String username, String ip, boolean status, String description, String userAgent) {
this.username = username;
this.ip = ip;
this.status = status;
this.description = description;
this.createTime = new Date();
this.userAgent = userAgent;
}
}
Moment(博客动态):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class Moment {
private Long id;
private String content;//动态内容
private Date createTime;//创建时间
private Integer likes;//点赞数量
private Boolean published;//是否公开
}
OperationLog(操作日志):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class OperationLog {
private Long id;
private String username;//操作者用户名
private String uri;//请求接口
private String method;//请求方式
private String param;//请求参数
private String description;//操作描述
private String ip;//ip
private String ipSource;//ip来源
private String os;//操作系统
private String browser;//浏览器
private Integer times;//请求耗时(毫秒)
private Date createTime;//操作时间
private String userAgent;
public OperationLog(String username, String uri, String method, String description, String ip, Integer times, String userAgent) {
this.username = username;
this.uri = uri;
this.method = method;
this.description = description;
this.ip = ip;
this.times = times;
this.createTime = new Date();
this.userAgent = userAgent;
}
}
ScheduleJob(定时任务类):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class ScheduleJob {
public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY"; //任务调度参数key
private Long jobId;//任务id
private String beanName;//spring bean名称
private String methodName;//方法名
private String params;//参数
private String cron;//cron表达式
private Boolean status;//任务状态
private String remark;//备注
private Date createTime;//创建时间
}
ScheduleJobLog(定时任务日志):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class ScheduleJobLog {
private Long logId;//日志id
private Long jobId;//任务id
private String beanName;//spring bean名称
private String methodName;//方法名
private String params;//参数
private Boolean status;//任务执行结果
private String error;//异常信息
private Integer times;//耗时(单位:毫秒)
private Date createTime;//创建时间
}
SiteSetting(站点设置):
@NoArgsConstructor
@Getter
@Setter
@ToString
@JsonIgnoreProperties(ignoreUnknown = true)
public class SiteSetting {
private Long id;
private String nameEn;
private String nameZh;
private String value;
private Integer type;
}
Tag(博客标签):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class Tag {
private Long id;
private String name;//标签名称
private String color;//标签颜色(与Semantic UI提供的颜色对应,可选)
private List<Blog> blogs = new ArrayList<>();//该标签下的博客文章
}
User(用户实体类):
@NoArgsConstructor
@Getter
@Setter
@ToString
//User实现spring的UserDetails类
public class User implements UserDetails {
private Long id;
private String username;
private String password;
private String nickname;
private String avatar;
private String email;
private Date createTime;
private Date updateTime;
private String role;
@JsonIgnore//序列化成Json时不包括此信息
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorityList = new ArrayList<>();
authorityList.add(new SimpleGrantedAuthority(role));
return authorityList;
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}
VisitLog(访问日志):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class VisitLog {
private Long id;
private String uuid;//访客标识码
private String uri;//请求接口
private String method;//请求方式
private String param;//请求参数
private String behavior;//访问行为
private String content;//访问内容
private String remark;//备注
private String ip;//ip
private String ipSource;//ip来源
private String os;//操作系统
private String browser;//浏览器
private Integer times;//请求耗时(毫秒)
private Date createTime;//访问时间
private String userAgent;
public VisitLog(String uuid, String uri, String method, String behavior, String content, String remark, String ip, Integer times, String userAgent) {
this.uuid = uuid;
this.uri = uri;
this.method = method;
this.behavior = behavior;
this.content = content;
this.remark = remark;
this.ip = ip;
this.times = times;
this.createTime = new Date();
this.userAgent = userAgent;
}
}
Visitor(访客类):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class Visitor {
private Long id;
private String uuid;//访客标识码
private String ip;//ip
private String ipSource;//ip来源
private String os;//操作系统
private String browser;//浏览器
private Date createTime;//首次访问时间
private Date lastTime;//最后访问时间
private Integer pv;//访问页数统计
private String userAgent;
public Visitor(String uuid, String ip, String userAgent) {
this.uuid = uuid;
this.ip = ip;
Date date = new Date();
this.createTime = date;
this.lastTime = date;
this.pv = 0;
this.userAgent = userAgent;
}
}
VisitRecord(访问记录):
@NoArgsConstructor
@Getter
@Setter
@ToString
public class VisitRecord {
private Long id;
private Integer pv;//访问量
private Integer uv;//独立用户
private String date;//日期"02-23"
public VisitRecord(Integer pv, Integer uv, String date) {
this.pv = pv;
this.uv = uv;
this.date = date;
}
}
mapper:
AboutMapper(关于我持久层接口):
@Mapper//注册成mapper
@Repository//表示为数据访问的bean
public interface AboutMapper {
List<About> getList();
int updateAbout(String nameEn, String value);
String getAboutCommentEnabled();
}
BlogMapper(博客文章持久层接口):
@Mapper
@Repository
public interface BlogMapper {
//使用博客标题和类型id查找博客
List<Blog> getListByTitleAndCategoryId(String title, Integer categoryId);
//查找相关关键字的Blog,searchBlog为关键字查找vo类
List<SearchBlog> getSearchBlogListByQueryAndIsPublished(String query);
List<Blog> getIdAndTitleList();
List<NewBlog> getNewBlogListByIsPublished();
List<BlogInfo> getBlogInfoListByIsPublished();
List<BlogInfo> getBlogInfoListByCategoryNameAndIsPublished(String categoryName);
List<BlogInfo> getBlogInfoListByTagNameAndIsPublished(String tagName);
List<String> getGroupYearMonthByIsPublished();
List<ArchiveBlog> getArchiveBlogListByYearMonthAndIsPublished(String yearMonth);
List<RandomBlog> getRandomBlogListByLimitNumAndIsPublishedAndIsRecommend(Integer limitNum);
List<BlogView> getBlogViewsList();
int deleteBlogById(Long id);
int deleteBlogTagByBlogId(Long blogId);
int saveBlog(top.naccl.model.dto.Blog blog);
int saveBlogTag(Long blogId, Long tagId);
int updateBlogRecommendById(Long blogId, Boolean recommend);
int updateBlogVisibilityById(Long blogId, BlogVisibility bv);
int updateBlogTopById(Long blogId, Boolean top);
int updateViews(Long blogId, Integer views);
Blog getBlogById(Long id);
String getTitleByBlogId(Long id);
BlogDetail getBlogByIdAndIsPublished(Long id);
String getBlogPassword(Long blogId);
int updateBlog(top.naccl.model.dto.Blog blog);
int countBlog();
int countBlogByIsPublished();
int countBlogByCategoryId(Long categoryId);
int countBlogByTagId(Long tagId);
Boolean getCommentEnabledByBlogId(Long blogId);
Boolean getPublishedByBlogId(Long blogId);
List<CategoryBlogCount> getCategoryBlogCountList();
}
CategoryMapper(博客分类持久层接口):
@Mapper
@Repository
public interface CategoryMapper {
List<Category> getCategoryList();
List<Category> getCategoryNameList();
int saveCategory(Category category);
Category getCategoryById(Long id);
Category getCategoryByName(String name);
int deleteCategoryById(Long id);
int updateCategory(Category category);
}
CityVisitorMapper(城市访客数量统计持久层接口):
@Mapper
@Repository
public interface CityVisitorMapper {
List<CityVisitor> getCityVisitorList();
int saveCityVisitor(CityVisitor cityVisitor);
}
CommentMapper(博客评论持久层接口):
@Mapper
@Repository
public interface CommentMapper {
List<Comment> getListByPageAndParentCommentId(Integer page, Long blogId, Long parentCommentId);
List<Comment> getListByParentCommentId(Long parentCommentId);
List<PageComment> getPageCommentListByPageAndParentCommentId(Integer page, Long blogId, Long parentCommentId);
Comment getCommentById(Long id);
int updateCommentPublishedById(Long commentId, Boolean published);
int updateCommentNoticeById(Long commentId, Boolean notice);
int deleteCommentById(Long commentId);
int deleteCommentsByBlogId(Long blogId);
int updateComment(Comment comment);
int countByPageAndIsPublished(Integer page, Long blogId, Boolean isPublished);
int countComment();
int saveComment(top.naccl.model.dto.Comment comment);
}
ExceptionLogMapper(异常日志持久层接口):
@Mapper
@Repository
public interface ExceptionLogMapper {
List<ExceptionLog> getExceptionLogListByDate(String startDate, String endDate);
int saveExceptionLog(ExceptionLog log);
int deleteExceptionLogById(Long id);
}
FriendMapper(友链持久层接口):
@Mapper
@Repository
public interface FriendMapper {
List<Friend> getFriendList();
List<top.naccl.model.vo.Friend> getFriendVOList();
int updateFriendPublishedById(Long id, Boolean published);
int saveFriend(Friend friend);
int updateFriend(top.naccl.model.dto.Friend friend);
int deleteFriend(Long id);
int updateViewsByNickname(String nickname);
}
LoginLogMapper(登录日志持久层接口):
@Mapper
@Repository
public interface LoginLogMapper {
List<LoginLog> getLoginLogListByDate(String startDate, String endDate);
int saveLoginLog(LoginLog log);
int deleteLoginLogById(Long id);
}
MomentMapper(博客动态持久层接口):
@Mapper
@Repository
public interface MomentMapper {
List<Moment> getMomentList();
int addLikeByMomentId(Long momentId);
int updateMomentPublishedById(Long momentId, Boolean published);
Moment getMomentById(Long id);
int deleteMomentById(Long id);
int saveMoment(Moment moment);
int updateMoment(Moment moment);
}
OperationLogMapper(操作日志持久层接口):
@Mapper
@Repository
public interface OperationLogMapper {
List<OperationLog> getOperationLogListByDate(String startDate, String endDate);
int saveOperationLog(OperationLog log);
int deleteOperationLogById(Long id);
}
ScheduleJobLogMapper(定时任务日志持久层接口):
@Mapper
@Repository
public interface ScheduleJobLogMapper {
List<ScheduleJobLog> getJobLogListByDate(String startDate, String endDate);
int saveJobLog(ScheduleJobLog jobLog);
int deleteJobLogByLogId(Long logId);
}
ScheduleJobMapper(定时任务持久层接口):
@Mapper
@Repository
public interface ScheduleJobMapper {
List<ScheduleJob> getJobList();
ScheduleJob getJobById(Long jobId);
int saveJob(ScheduleJob scheduleJob);
int updateJob(ScheduleJob scheduleJob);
int deleteJobById(Long jobId);
int updateJobStatusById(Long jobId, Boolean status);
}
SiteSettingMapper(站点设置持久层接口):
@Mapper
@Repository
public interface SiteSettingMapper {
List<SiteSetting> getList();
List<SiteSetting> getFriendInfo();
String getWebTitleSuffix();
int updateSiteSetting(SiteSetting siteSetting);
int deleteSiteSettingById(Integer id);
int saveSiteSetting(SiteSetting siteSetting);
int updateFriendInfoContent(String content);
int updateFriendInfoCommentEnabled(Boolean commentEnabled);
}
TagMapper(博客标签持久层接口):
@Mapper
@Repository
public interface TagMapper {
List<Tag> getTagList();
List<Tag> getTagListNotId();
List<Tag> getTagListByBlogId(Long blogId);
int saveTag(Tag tag);
Tag getTagById(Long id);
Tag getTagByName(String name);
int deleteTagById(Long id);
int updateTag(Tag tag);
List<TagBlogCount> getTagBlogCount();
}
UserMapper(用户持久层接口):
@Mapper
@Repository
public interface UserMapper {
User findByUsername(String username);
User findById(Long id);
int updateUserByUsername(String username, User user);
}
VisitLogMapper(访问日志持久层接口):
@Mapper
@Repository
public interface VisitLogMapper {
List<VisitLog> getVisitLogListByUUIDAndDate(String uuid, String startDate, String endDate);
List<VisitLogUuidTime> getUUIDAndCreateTimeByYesterday();
int saveVisitLog(VisitLog log);
int deleteVisitLogById(Long id);
int countVisitLogByToday();
}
VisitorMapper(访客统计持久层接口):
@Mapper
@Repository
public interface VisitorMapper {
List<Visitor> getVisitorListByDate(String startDate, String endDate);
List<String> getNewVisitorIpSourceByYesterday();
int hasUUID(String uuid);
int saveVisitor(Visitor visitor);
int updatePVAndLastTimeByUUID(VisitLogUuidTime dto);
int deleteVisitorById(Long id);
}
VisitRecordMapper(访问记录持久层接口):
@Mapper
@Repository
public interface VisitRecordMapper {
List<VisitRecord> getVisitRecordListByLimit(Integer limit);
int saveVisitRecord(VisitRecord visitRecord);
}
Controller:
AboutController(关于我页面):
@RestController
public class AboutController {
@Autowired
AboutService aboutService;
/**
* 获取关于我页面信息
*
* @return
*/
@VisitLogger(VisitBehavior.ABOUT)
@GetMapping("/about")
public Result about() {
return Result.ok("获取成功", aboutService.getAboutInfo());
}
}
annotation类(自定义注解类):
AccessLimit(访问限制):
@Target(ElementType.METHOD)//定义注解的作用目标(作用域)
@Retention(RetentionPolicy.RUNTIME)//修饰注解,用来表示注解的生命周期,生命周期的长短取决于@Retention的属性RetentionPolicy指定的值,例如
public @interface AccessLimit {
/**
* 限制周期(秒)
*/
int seconds();
/**
* 规定周期内限制次数
*/
int maxCount();
/**
* 触发限制时的消息提示
*/
String msg() default "操作频率过高";
}
OperationLogger(用于需要记录操作日志的方法):
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLogger {
/**
* 操作描述
*/
String value() default "";
}
VisitLogger(用于需要记录访客访问日志的方法):
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface VisitLogger {
/**
* 访问行为枚举
*/
VisitBehavior value() default VisitBehavior.UNKNOWN;
}
aspect类(AOP开发模式):
ExceptionLogAspect类(AOP记录异常日志):
后续会补充...