初版代码提交

This commit is contained in:
冯聪聪
2026-05-17 15:55:06 +08:00
parent 208212d84c
commit c50669ee36
70 changed files with 3647 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
<?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>fun.nojava</groupId>
<artifactId>jog-system</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>jog-common</artifactId>
<name>Jog Common</name>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,18 @@
package fun.nojava.common.annotation;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
String key() default "";
int maxCount() default 10;
int duration() default 60;
TimeUnit unit() default TimeUnit.SECONDS;
}
@@ -0,0 +1,14 @@
package fun.nojava.common.constant;
public final class BlogConstants {
private BlogConstants() {}
public static final int DEFAULT_PAGE_SIZE = 15;
public static final int MAX_PAGE_SIZE = 100;
public static final int ARTICLE_SUMMARY_LENGTH = 120;
public static final int MAX_TAGS_PER_ARTICLE = 5;
public static final int MAX_COMMENT_LEVEL = 3;
public static final int IMAGE_MAX_SIZE_MB = 10;
public static final long DRAFT_AUTO_SAVE_INTERVAL_SECONDS = 30L;
}
@@ -0,0 +1,29 @@
package fun.nojava.common.constant;
public final class SecurityConstants {
private SecurityConstants() {}
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
public static final String REFRESH_TOKEN_KEY = "refresh:token:";
public static final String CAPTCHA_KEY = "captcha:";
public static final String LOGIN_FAIL_COUNT_KEY = "login:fail:";
public static final String IP_BLOCK_KEY = "ip:block:";
public static final String REGISTER_LIMIT_KEY = "register:limit:";
public static final int MAX_LOGIN_FAIL_ADMIN = 5;
public static final int MAX_LOGIN_FAIL_USER = 5;
public static final int CAPTCHA_TRIGGER_COUNT = 3;
public static final int ADMIN_LOCK_MINUTES = 30;
public static final int USER_LOCK_MINUTES = 15;
public static final int IP_BLOCK_THRESHOLD = 10;
public static final int IP_BLOCK_MINUTES = 15;
public static final int REGISTER_LIMIT_PER_HOUR = 3;
public static final int MAX_ACTIVE_SESSIONS = 3;
public static final long ACCESS_TOKEN_EXPIRE_SECONDS = 7200L;
public static final long REFRESH_TOKEN_EXPIRE_SECONDS = 86400L;
public static final long REFRESH_TOKEN_REMEMBER_EXPIRE_SECONDS = 1209600L;
}
@@ -0,0 +1,25 @@
package fun.nojava.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ArticleStatus {
DRAFT(0, "草稿"),
PUBLISHED(1, "已发布"),
RECYCLE(2, "回收站");
private final int code;
private final String description;
public static ArticleStatus fromCode(int code) {
for (ArticleStatus status : values()) {
if (status.code == code) {
return status;
}
}
throw new IllegalArgumentException("Invalid ArticleStatus code: " + code);
}
}
@@ -0,0 +1,25 @@
package fun.nojava.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ArticleVisibility {
PUBLIC(0, "公开"),
PRIVATE(1, "私密"),
FOLLOWERS_ONLY(2, "仅粉丝可见");
private final int code;
private final String description;
public static ArticleVisibility fromCode(int code) {
for (ArticleVisibility v : values()) {
if (v.code == code) {
return v;
}
}
throw new IllegalArgumentException("Invalid ArticleVisibility code: " + code);
}
}
@@ -0,0 +1,26 @@
package fun.nojava.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum CommentStatus {
PENDING(0, "待审核"),
APPROVED(1, "已通过"),
BLOCKED(2, "已屏蔽"),
DELETED(3, "已删除");
private final int code;
private final String description;
public static CommentStatus fromCode(int code) {
for (CommentStatus status : values()) {
if (status.code == code) {
return status;
}
}
throw new IllegalArgumentException("Invalid CommentStatus code: " + code);
}
}
@@ -0,0 +1,15 @@
package fun.nojava.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum LoginType {
ADMIN(0, "管理员登录"),
USER(1, "用户登录");
private final int code;
private final String description;
}
@@ -0,0 +1,16 @@
package fun.nojava.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum NotificationType {
COMMENT_REPLY(0, "回复评论"),
LIKE(1, "点赞"),
SYSTEM(2, "系统通知");
private final int code;
private final String description;
}
@@ -0,0 +1,24 @@
package fun.nojava.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum UserStatus {
NORMAL(0, "正常"),
LOCKED(1, "锁定");
private final int code;
private final String description;
public static UserStatus fromCode(int code) {
for (UserStatus status : values()) {
if (status.code == code) {
return status;
}
}
throw new IllegalArgumentException("Invalid UserStatus code: " + code);
}
}
@@ -0,0 +1,24 @@
package fun.nojava.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum UserType {
ADMIN(0, "超级管理员"),
USER(1, "注册用户");
private final int code;
private final String description;
public static UserType fromCode(int code) {
for (UserType type : values()) {
if (type.code == code) {
return type;
}
}
throw new IllegalArgumentException("Invalid UserType code: " + code);
}
}
@@ -0,0 +1,24 @@
package fun.nojava.common.exception;
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException {
private final int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
public BusinessException(ErrorCode errorCode, String message) {
super(message);
this.code = errorCode.getCode();
}
}
@@ -0,0 +1,48 @@
package fun.nojava.common.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ErrorCode {
BAD_REQUEST(400, "参数错误"),
UNAUTHORIZED(401, "未认证"),
FORBIDDEN(403, "无权限"),
NOT_FOUND(404, "资源不存在"),
METHOD_NOT_ALLOWED(405, "请求方法不允许"),
CONFLICT(409, "操作冲突"),
RATE_LIMITED(429, "请求过于频繁"),
INTERNAL_ERROR(500, "服务器内部错误"),
ACCOUNT_LOCKED(1001, "账号已被锁定"),
ACCOUNT_DISABLED(1002, "账号已被禁用"),
EMAIL_NOT_VERIFIED(1003, "邮箱未验证"),
EMAIL_ALREADY_EXISTS(1004, "邮箱已被注册"),
PASSWORD_TOO_WEAK(1005, "密码强度不足"),
LOGIN_FAIL_TOO_MANY(1006, "登录失败次数过多"),
IP_BLOCKED(1007, "IP已被临时封禁"),
CAPTCHA_REQUIRED(1008, "需要输入验证码"),
CAPTCHA_INVALID(1009, "验证码无效"),
TOKEN_INVALID(1010, "Token无效"),
TOKEN_EXPIRED(1011, "Token已过期"),
SESSION_KICKED(1012, "会话已被踢出"),
ARTICLE_NOT_FOUND(2001, "文章不存在"),
ARTICLE_NO_PERMISSION(2002, "无权操作该文章"),
TAG_LIMIT_EXCEEDED(2003, "标签数量超出限制"),
CATEGORY_NOT_FOUND(2004, "分类不存在"),
CATEGORY_DISABLED(2005, "分类已禁用"),
CATEGORY_HAS_ARTICLES(2006, "分类下存在文章,无法删除"),
UPLOAD_FILE_INVALID(2007, "上传文件不合法"),
UPLOAD_FILE_TOO_LARGE(2008, "上传文件过大"),
COMMENT_NOT_FOUND(3001, "评论不存在"),
COMMENT_AUDIT_PENDING(3002, "评论待审核"),
COMMENT_NO_PERMISSION(3003, "无权操作该评论"),
COMMENT_LEVEL_EXCEEDED(3004, "评论层级超限");
private final int code;
private final String message;
}
@@ -0,0 +1,47 @@
package fun.nojava.common.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(description = "统一响应结果")
public class ApiResult<T> {
@Schema(description = "业务状态码")
private int code;
@Schema(description = "提示信息")
private String message;
@Schema(description = "响应数据")
private T data;
@Schema(description = "响应时间戳")
private long timestamp;
public static <T> ApiResult<T> success() {
return new ApiResult<>(200, "success", null, System.currentTimeMillis());
}
public static <T> ApiResult<T> success(T data) {
return new ApiResult<>(200, "success", data, System.currentTimeMillis());
}
public static <T> ApiResult<T> success(String message, T data) {
return new ApiResult<>(200, message, data, System.currentTimeMillis());
}
public static <T> ApiResult<T> error(int code, String message) {
return new ApiResult<>(code, message, null, System.currentTimeMillis());
}
public static <T> ApiResult<T> error(fun.nojava.common.exception.ErrorCode errorCode) {
return new ApiResult<>(errorCode.getCode(), errorCode.getMessage(), null, System.currentTimeMillis());
}
}
@@ -0,0 +1,16 @@
package fun.nojava.common.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "ID返回")
public class IdVO {
@Schema(description = "ID")
private Long id;
}
@@ -0,0 +1,20 @@
package fun.nojava.common.model;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import lombok.Data;
@Data
@Schema(description = "分页请求参数")
public class PageParam {
@Schema(description = "页码,从1开始")
@Min(value = 1, message = "页码最小为1")
private int page = 1;
@Schema(description = "每页条数")
@Min(value = 1, message = "每页条数最小为1")
@Max(value = 100, message = "每页条数最大为100")
private int pageSize = 15;
}
@@ -0,0 +1,27 @@
package fun.nojava.common.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "分页结果")
public class PageResult<T> {
@Schema(description = "数据列表")
private List<T> records;
@Schema(description = "总记录数")
private long total;
@Schema(description = "当前页码")
private int page;
@Schema(description = "每页条数")
private int pageSize;
}
@@ -0,0 +1,27 @@
package fun.nojava.common.util;
import fun.nojava.common.model.ApiResult;
import fun.nojava.common.model.PageResult;
import org.springframework.data.domain.Page;
import java.util.List;
public final class ApiResultUtils {
private ApiResultUtils() {}
public static <T> ApiResult<PageResult<T>> page(List<T> records, long total, int page, int pageSize) {
PageResult<T> pageResult = new PageResult<>(records, total, page, pageSize);
return ApiResult.success(pageResult);
}
public static <T> ApiResult<PageResult<T>> page(Page<T> springPage) {
PageResult<T> pageResult = new PageResult<>(
springPage.getContent(),
springPage.getTotalElements(),
springPage.getNumber() + 1,
springPage.getSize()
);
return ApiResult.success(pageResult);
}
}