Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions OpenBlog-business/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>com.yqz</groupId>
<artifactId>OpenBlog-common</artifactId>
<version>${project.version}</version>
</dependency>

<!-- Dubbo + Nacos (consumer) -->
<dependency>
<groupId>org.apache.dubbo</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@
import com.yqz.openblog.seo.SeoProperties;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.scheduling.annotation.EnableScheduling;

/**
* 显式声明组件扫描(覆盖 @SpringBootApplication 的默认扫描):
* - basePackages 与默认一致;额外排除 com.yqz.openblog.common 包——
* 该包的异常处理器改由 OpenBlog-common 的自动装配(CommonAutoConfiguration)注册。
* - 显式 @ComponentScan 不会自动带上 @SpringBootApplication 默认的两个过滤,
* 因此手动补回 TypeExcludeFilter / AutoConfigurationExcludeFilter,保证
* framework 模块下的 @AutoConfiguration 类不被组件扫描重复注册。
*/
@SpringBootApplication
@EnableDubbo
@EnableScheduling
Expand All @@ -19,6 +31,13 @@
AuthSecurityProperties.class,
SeoProperties.class
})
@ComponentScan(
basePackages = "com.yqz.openblog",
excludeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class),
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "com\\.yqz\\.openblog\\.common\\..*")
})
public class OpenBlogApplication {

public static void main(String[] args) {
Expand Down
34 changes: 34 additions & 0 deletions OpenBlog-common/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?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>com.yqz</groupId>
<artifactId>OpenBlog</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>

<artifactId>OpenBlog-common</artifactId>
<name>OpenBlog-common</name>
<description>OpenBlog common module — unified REST response, exceptions, and utilities</description>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDeniedException;

/**
* 统一异常处理(MVP)
* 统一异常处理(OpenBlog-common)。通过 CommonAutoConfiguration 自动装配注册
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
Expand All @@ -38,15 +36,6 @@ public ResponseEntity<ApiResponse<Object>> onValidation(MethodArgumentNotValidEx
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.fail(4001, msg));
}

/**
* 方法级鉴权(@PreAuthorize)失败会抛出 AuthorizationDeniedException;
* 以前会落到兜底 Exception -> 5001,导致前端误判为“服务器异常”。
*/
@ExceptionHandler({AuthorizationDeniedException.class, AccessDeniedException.class})
public ResponseEntity<ApiResponse<Object>> onAccessDenied(Exception ex) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiResponse.fail(4030, "无权限"));
}

/**
* IO 异常(MinIO 读写、文件读写等)直接返回原始错误信息,便于排查。
*/
Expand All @@ -64,4 +53,3 @@ public ResponseEntity<ApiResponse<Object>> onOther(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ApiResponse.fail(5001, msg));
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.yqz.openblog.common;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
* 安全相关异常处理,仅在 classpath 存在 spring-security 时由 CommonAutoConfiguration 装配。
* (email 等无 security 依赖的服务自动跳过。)
*/
@RestControllerAdvice
public class SecurityExceptionHandler {

/**
* 方法级鉴权(@PreAuthorize)失败会抛出 AuthorizationDeniedException;
* 以前会落到兜底 Exception -> 5001,导致前端误判为“服务器异常”。
*/
@ExceptionHandler({AuthorizationDeniedException.class, AccessDeniedException.class})
public ResponseEntity<ApiResponse<Object>> onAccessDenied(Exception ex) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiResponse.fail(4030, "无权限"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.yqz.openblog.common.config;

import com.yqz.openblog.common.GlobalExceptionHandler;
import com.yqz.openblog.common.SecurityExceptionHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;

/**
* OpenBlog-common 自动装配:注册统一异常处理器。
* 注意:调用方组件扫描应排除 com.yqz.openblog.common.*,避免与自动装配重复注册。
*/
@AutoConfiguration
@ConditionalOnWebApplication
public class CommonAutoConfiguration {

@Bean
@ConditionalOnMissingBean(GlobalExceptionHandler.class)
public GlobalExceptionHandler globalExceptionHandler() {
return new GlobalExceptionHandler();
}

@Bean
@ConditionalOnClass(name = "org.springframework.security.access.AccessDeniedException")
@ConditionalOnMissingBean(SecurityExceptionHandler.class)
public SecurityExceptionHandler securityExceptionHandler() {
return new SecurityExceptionHandler();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.yqz.openblog.common.config.CommonAutoConfiguration
7 changes: 7 additions & 0 deletions OpenBlog-email/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
<version>${project.version}</version>
</dependency>

<!-- Common (统一响应结构 + 全局异常处理器) -->
<dependency>
<groupId>com.yqz</groupId>
<artifactId>OpenBlog-common</artifactId>
<version>${project.version}</version>
</dependency>

<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.yqz.openblog.email.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yqz.openblog.common.ApiResponse;
import com.yqz.openblog.common.PageResult;
import com.yqz.openblog.email.api.EmailSendRequest;
import com.yqz.openblog.email.api.EmailSendResult;
import com.yqz.openblog.email.dto.EmailRecordResponse;
import com.yqz.openblog.email.service.EmailService;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/api/v1/email")
@CrossOrigin(origins = "*")
Expand All @@ -24,25 +24,21 @@ public EmailAdminController(EmailService emailService) {
* 快速测试发送邮件:POST /api/v1/email/test?recipient=xxx&subject=xxx&body=xxx
*/
@PostMapping("/test")
public EmailSendResult testSend(
public ApiResponse<EmailSendResult> testSend(
@RequestParam String recipient,
@RequestParam String subject,
@RequestParam String body) {
EmailSendRequest req = new EmailSendRequest(recipient, subject, body);
return emailService.send(req);
return ApiResponse.ok(emailService.send(req));
}

@GetMapping("/records")
public Map<String, Object> listRecords(
public ApiResponse<PageResult<EmailRecordResponse>> listRecords(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String status) {
IPage<EmailRecordResponse> p = emailService.listRecords(page, size, status);
return Map.of(
"items", p.getRecords(),
"total", p.getTotal(),
"page", page,
"size", size
);
PageResult<EmailRecordResponse> pr = new PageResult<>(p.getRecords(), page, size, p.getTotal());
return ApiResponse.ok(pr);
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

| 模块 | 说明 |
|------|------|
| `OpenBlog-common` | 公共模块:统一响应 `ApiResponse`、`PageResult`、`BizException`、异常处理自动装配 |
| `OpenBlog-business` | 主业务服务(端口 8082):文章、评论、论坛、用户、认证、SEO、媒体管理 |
| `OpenBlog-email` | 独立邮件服务(端口 8083):阿里云 DirectMail + Dubbo RPC + Nacos 注册 |
| `OpenBlog-api` | 共享 API 聚合模块 |
Expand Down
Loading