常规项目开发全景图
补充: 本图聚焦于应用层的主要业务流转与代码架构。不过,实际生产环境中,项目的平稳运行还依赖于运维/基础架构团队提供的底层环境 。
一、流量接入与路由层
Nginx 与 CDN
运维负责配置反向代理、负载均衡和静态资源加速。至于视角,本地联调时前端直连后端端口;测试/生产环境请求先打到 Nginx 再转发。怎么说呢,
API 网关
统一路由、跨域、限流、鉴权等。再看视角,Controller 接口方法会被网关统一加前缀。例如本地 /api/users → 网关暴露为 /talent-service/api/users。
二、微服务治理与任务调度层
Nacos 配置中心
所有 Spring 容器的配置均从此拉取。本地使用 application.yml部署时优先读取 Nacos。
XXL‑JOB 定时任务中心
编写业务方法并加 @XxlJob 注解,后台通过可视化页面调度。
三、可观测性与监控追踪层
ELK 日志程序
运维负责日志采集、清洗、存储,提供 Kibana 看板。必须使用规范日志框架并打印关键业务标识或 traceId。
Promeus & Grafana 监控程序
监控 QPS、响应延迟、CPU/内存水位。
SkyWalking 链路追踪
打开拓扑图输入 traceId定位跨服务调用链路。
痛点一: 新手经常忘记在代码里打印业务关键字或 traceId导致线上定位困难。
痛点二: Nacos 配置未同步导致本地和线上环境行为不一致。
痛点三: ID 精度丢失——前端 JS 无法安全处理超长 Long 主键,需要统一序列化为字符串。
三层架构全景详细流转
Controller 层
职责: 接收 HTTP 请求。完成 JSON 反序列化或表单解析,将干净的 DTO 交给 Service;统一包装返回结果为 JSON。
@RestController + @RequestMapping:
@RestController
@RequestMapping
public class UserController {
@Autowired private UserService userService;@GetMapping
public Result getUserById {
UserVO vo = userService.getUserById;怎么说呢,return Result.success;}
}
Pain Point: 把业务逻辑写进 Controller 会导致代码臃肿且难以复用。
Service 层 & 接口+实现类模式
AOP 事务代理依赖接口: If you use JDK dynamic proxies,Spring can weave @Transactional correctly. Using only a concrete class forces CGLIB proxying which may miss transaction boundaries.
Poor testability without interface: Makes it hard to mock Service in unit tests.
The interface defines contract only;implementation contains business logic.
// Service Interface
public interface UserService {
UserVO getUserById;}
// Implementation
@Service
public class UserServiceImpl implements UserService {
@Autowired private UserMapper userMapper;@Override
public UserVO getUserById {
if throw new BusinessException;User user = userMapper.selectById;if throw new BusinessException;按理说,// 转 VO,过滤敏感字段
UserVO vo = new UserVO;BeanUtils.copyProperties;return vo,不过,}
}
Pain Point: 没有接口时一旦忘记在方法上加 @Transactional。就会出现脏数据回滚失败的问题。
Mapper 层
@Mapper 接口继承 MyBatis‑Plus 的 BaseMapper。即可获得 CRUD 方法,无需手写 SQL。
@Mapper
public interface UserMapper extends BaseMapper { /* 自动拥有 selectById。insert ... */ }
Pain Point: 误把业务判断写进 Mapper,会导致 DAO 层职责混乱且难以维护。
开启服务器初始化过程
自动注册 DispatcherServlet: SprintBoot 的 DispatcherServletAutoConfiguration 把前端控制器拦截所有方法 .
.ComponentScan 扫描:@SpringBootApplication 包含 @ComponentScan。仅扫描主类所在包及其子包,将带有 @RestController/@Service/@Repository 等注解的类注册为 Bean.
.HandlerMapping 建立路由表:Sprint MVC 解析所有 @RequestMapping/@GetMapping 等注解,将 URL 与具体方法映射到内存中的路由表。启动日志中会打印类似 “Mapped "{,methods=}"”.
Pain Point:忘记把 Controller 放在主类子包下导致 “NoSuchBeanDefinitionException”。说到解决办法,确认主类所在包是根包或使用 scanBasePackages 手动指定扫描范围。
单次请求处理过程
"进入 DispatcherServlet" : Tomcat 将请求交给 DispatcherServlet.
至于b.方法匹配,DispatcherServlet 使用 HandlerMapping 查找匹配的方法.
C. 参数绑定:@PathVariable / @RequestParam / @RequestBody 将 HTTP 参数自动转换注入方法形参.
D. 方法执领域务逻辑: 调用 Service → Mapper → DB.
E. 响应处理:
If 方法标记了 @ResponseBody / 类上有 @RestController。则跳过视图解析器,直接走 HttpMessageConverter 把返回对象序列化为 JSON 写入响应体.
. 默认加载规则
@SpringBootApplication 包含了 @ComponentScan,只会扫描MainClass 所在包及其子包* . 所以所有 Controller/Service/Config 必须放在该根包下否则会出现“找不到 Bean”错误。
. 手动扩大扫描范围
@SpringBootApplication
public class Application { public static void main{ SpringApplication.run;} }
Pain Point: 一旦手动指定 scanBasePackages。必须把主根包也写进去,否则原有组件都会失效!
. 排除特定 Bean 或自动装配类
@SpringBootApplication : 用于完全自定义数据源时排除 Spring Boot 默认 DataSource 自动装配.
Pain Point: 排除后忘记自行注册 DataSource 导致 MyBatis 启动报 NoSuchBeanDefinitionException。再看方法,在自定义 Config 中手动声明 DataSource Bean。
b. 按注解排除组件:
@ComponentScan(basePackages = "com.example",excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = IgnoreBean.class))
public class AppConfig {}
b. 按类型排除组件:
@ComponentScan(basePackages = "com.example"。excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = LegacyService.class))
public class AppConfig {}
. 创建工作空间
\
打开 PostMan → Workspaces → + New Workspace → 填写名称 & 可见性 → Create Workspace。
\
\
\
\
\
...
\
...
\
...
\
...
\
...
...
...
...
...
....
...
.....
...
--
....
....
....
...
...
—
—
–
…———
,…,…,…,…,…,…,…,…,…,…,…,…,…,…,…,…,…,…,…,.
…,…,…,…,…,…,…,….
———
———
———
—
———
—
**创建工作空间**
1️⃣ 打开 PostMan 应用 → 左上角 **Workspaces** 下拉 → **+ New Workspace**
2️⃣ 输入名称·选择可见性 → **Create Workspace**
**发送请求**
1️⃣ 新建标签页 “+”
2️⃣ 设置 Method 、URL 、Headers 、Body。3️⃣ 点击 **Send** 查看 Response。**保存请求**
点击标签页右侧 **Save** → 选择或新建 Collection → 命名后保存。其实,*⚠️ Pain point*: 新手经常直接在浏览器地址栏敲 POST 请求。以为能直接发送,需要 PostMan 或类似工具来发送非 GET 请求体。
`@RestController` + `@RequestMapping` 为模块级别根方法;按理说,方法级别再拼接操作方法。最终 URL 为「类级别方法 + 方法级别方法」。斜杠 `/` 自动拼接,不必担心重复或缺失斜杠导致 404。
. 场景一:根方法 + 具体操作
`@RestController`
`@RequestMapping`
`@GetMapping` ⇒ URL: `/health`
. 场景二:模块前缀 + 空字符串
`@RestController`
`@RequestMapping`
`@GetMapping` ⇒ URL: `/api/talents`
. 场景三:仅根斜杠
`@RestController`
`@RequestMapping`
`@GetMapping` ⇒ URL: `/`
⚡️ 痛点提示:多个 Controller 中若都有 `@RequestMapping` 而未加模块前缀,会导致启动时报 “Ambiguous mapping” 异常!方法是一定要给每个 Controller 加上业务模块前缀。如 `/api/talents`,`/api/companies` 等。
@RequestParam 参数接收与绑定机制 🔧 Pain Points Highlighted–––––‑‑‑
✦✦✦✦✦✦✦✦✦✧⸝⸝⸝⸝⸝⸝⸝⌙⌙⌙⌙⌙⌙⌙ⓣⓣⓣⓣⓣⓞ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 🌀🌀🌀🌀🌀🌀🌀🌀🌀💥💥💥💥💥💥💥💥💥🎱🎱🎱🎱🎱 🎲 🎲 🎲 🎲 🎲 🎰 🎰 🎰 🎰 🎰 🧩 🧩 🧩 🧩 🧩 🔮🔮🔮🔮🔮 ✨✨✨✨✨ ✨🌟🌟🌟🌟🌟 🌞 🌞 🌞 🌞 🌞 ☕️☕️☕️☕️☕️ 🍵🍵🍵🍵🍵 🍶 🍶 🍶 🍶 🍶 📚📚📚📚📚 📖 📖 📖 📖 📖 💡 💡 💡 💡 💡 🚀🚀🚀🚀🚀 🚤 🚤 🚤 🚤 🚤 🛳️🛳️🛳️🛳️🛳︎︎︎︎︎︎︎︎︎🙈🙈🙈🙈🙈 🙉 🙉 🙉 🙉 🙉 👓 👓 👓 👓 👓 😎 😎 😎 😎 😎 🤔🤔🤔🤔🤔 😴😴😴😴😴 🤓🤓🤓🤓🤓 😇 😇 😇 😇 😇 🙏🙏🙏🙏🙏
``
### 基础语法公式:
text
@RequestParam 数据类型 参数名
* `value / name`: 前端传递的真实键名。* `required`: 是否必传,默认 true。按理说,* `defaultValue`: 未传递时使用的默认值;设置后 required 自动变 false。#### 示例一:参数名不一致
java
@GetMapping
public Result search String name){
// 前端?keyword=张三,实际变量名叫 name
}
#### 示例二:分页查询默认值
java
@GetMapping
public Result page(
@RequestParam Integer pageNum。@RequestParam Integer pageSize){
return Result.success));}
#### 基础坑点:
* **基本类型 vs 包装类**
- 使用 `int age` 时如果前端未传递 `age` 会抛 `TypeMismatchException`.
- 改为 `Integer age` 可安全接受 `null`.
* **GET 与 POST 共用同一绑定方式** – 同一个接口不论 GET 还是 POST,都可以使用 `@RequestParam`;前提是 Content-Type 为 *application/x-www-form-urlencoded*。