最近自己在折腾全栈 DDD 架构时,踩了一些"坑",也有不少思考,特此记录。这篇文章适合有一点 Java 基础、正在搞全栈、追求代码结构和工程规范的你。
1. 背景与业务场景
做后台服务,身份认证登录注册总是绕不过去,特别是要支持"邮箱、手机号、第三方登录(微信、QQ、Github)"这种后续可扩展需求时,表设计就很重要了。
我的目标很简单:
- 数据库要能灵活扩展多种登录方式
- 业务分层要体现DDD思想
- 后端要多数据源适配(PostgreSQL为主,MySQL也能兼容)
- 前后端联调不被安全框架挡住路
- 关键配置要清楚,后续不踩重复的坑
2. 表结构这样设计,未来少掉头发
大致SQL如下,推荐每一列都写上中文注释(强烈建议!):
sql
CREATE TABLE identity.users (
id BIGSERIAL PRIMARY KEY, -- 用户ID
email VARCHAR(64) UNIQUE, -- 邮箱
phone VARCHAR(32) UNIQUE, -- 手机号
password VARCHAR(255) NOT NULL, -- 密码
nickname VARCHAR(64), -- 昵称
avatar VARCHAR(255), -- 头像
is_email_verified BOOLEAN DEFAULT FALSE, -- 邮箱是否已验证
is_phone_verified BOOLEAN DEFAULT FALSE, -- 手机号是否已验证
status SMALLINT DEFAULT 1, -- 状态
last_login_at TIMESTAMP, -- 上次登录时间
created_at TIMESTAMP DEFAULT now(), -- 创建时间
updated_at TIMESTAMP DEFAULT now(), -- 更新时间
created_by VARCHAR(64), -- 创建人
updated_by VARCHAR(64), -- 修改人
wechat_unionid VARCHAR(64), -- 微信unionId(扩展)
qq_openid VARCHAR(64), -- QQ openId(扩展)
github_id VARCHAR(64), -- github账号(扩展)
version INT DEFAULT 0, -- 乐观锁
is_deleted BOOLEAN DEFAULT FALSE -- 软删除
);
小建议:
表名、字段名都尽量"意图明确",小写蛇形,严格区分限界上下文(如 identity.users)。写注释不仅是对自己负责,也是对团队和未来的你负责。
3. 多数据源这样配,PostgreSQL / MySQL 随便切
用 dynamic-datasource-spring-boot-starter,YAML配置极其简单(节选):
yaml
spring:
datasource:
dynamic:
primary: pg
datasource:
pg:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/your_db?currentSchema=identity
username: postgres
password: your_password
mysql:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/your_db?serverTimezone=Asia/Shanghai
username: root
password: your_password
想切库,代码里加@DS("mysql")
或@DS("pg")
即可。是不是有种"撸起袖子就能干"的感觉?
4. 你的后端代码,分层要自觉
领域层、应用层、基础设施层、接口层......大家都懂,但真能做到清晰分层,其实是对自我约束的磨炼。
一个标准认证流程,推荐结构如下:
- Controller(接口层,处理请求响应)
- Service(应用层,编排业务逻辑)
- Mapper(基础设施层,数据库操作,MyBatis-Plus自动管理)
- Domain(领域层,定义实体/聚合)
代码示例:
java
// Mapper接口
@Mapper
public interface UserMapper extends BaseMapper<User> {}
// Service接口
public interface UserService {
User register(User user);
User login(String email, String password);
}
// Service实现
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
// ...具体逻辑
}
人话解释:
Controller 不要注入 Mapper,也不要写 if else 业务细节。分层,是为了"今天自己看、明天同事看、后天重构都不难受"。
5. 说一说 MyBatis-Plus 的"坑"
最大的大坑:@MapperScan配置太宽导致BindingException!
真实血泪:
java
@MapperScan("pers.seekersferry.*") // ❌ 致命!把service等接口全当Mapper扫描了!
结果所有的service接口也被MyBatis-Plus"当成Mapper"去找SQL语句,必然报错:
bash
Invalid bound statement (not found): pers.seekersferry.identity.application.service.UserService.register
修正方案:
java
@MapperScan("pers.seekersferry.identity.infrastructure.mapper")
只扫描Mapper包,永远不要一把梭所有包。
另一个常见误区:@Mapper写错地方
@Mapper 只写在Mapper接口(如UserMapper),绝不能写在Service接口。
6. Spring Security 默认401/403的那些"小惊喜"
很多人一开始加上spring-boot-starter-security
就疯了------接口全部 401,swagger也打不开,开发体验直接爆炸。
人话解决方案:
本地开发阶段,把所有接口都放开(安全的事以后再说),加一个安全配置类:
java
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authorize -> authorize
.anyRequest().permitAll()
);
return http.build();
}
}
友情提醒:
生产环境一定要收紧权限,但开发联调阶段别给自己添堵。
7. @MapperScan能写在yaml里吗?
答案很干脆:不能!
- yaml 只能配 mapper-locations、type-aliases-package 这类属性,扫描 Mapper 包必须在 Java 里用注解或者配置类。
- 以后看到"为什么我的service也被当Mapper了?"第一反应就该查MapperScan!
8. 结语:规范不是负担,是"防脱发"的最佳姿势
每一次规范的小坚持,都是在为自己的效率和团队的可维护性加分。
遇到bug和踩坑,别慌,debug和Google是最靠谱的好朋友。别让配置文件和注解把你绕晕,读源码、翻官方文档、反思自己的调用链,一步步就能爬出来。
希望这篇文章对同为全栈的你,有点共鸣、有点实用,也祝你的后端工程越来越稳,"长久在线,头发依旧"。
如果你有类似经历或者更骚气的踩坑,欢迎留言交流!