后端开发概述
作者:唐亚峰 | battcn
字数统计:2.2k 字
学习目标
掌握 Wemirr Platform 后端项目结构、开发规范和核心功能
技术栈
| 技术 | 版本 | 说明 |
|---|---|---|
| JDK | 17/21 | Java 开发工具包 |
| Spring Boot | 3.x | 基础框架 |
| Spring Cloud | 2024.x | 微服务框架 |
| Spring Cloud Alibaba | 2023.x | 阿里微服务组件 |
| Sa-Token | 最新版 | 轻量级权限认证框架 |
| MyBatis-Plus | 3.5.x | 增强版 MyBatis |
| Nacos | 2.x | 服务注册与配置中心 |
| Redis | 6+ | 缓存、分布式锁 |
| MySQL | 8.0 | 关系型数据库 |
项目结构
wemirr-platform/
├── wemirr-platform-dependencies/ # 📦 依赖版本管理(BOM)
├── wemirr-platform-framework/ # 🔧 框架核心层
│ ├── common-framework-core/ # 基础核心(注解、接口、工具类)
│ ├── common-spring-boot-starter/ # Spring Boot 核心启动器
│ ├── db-spring-boot-starter/ # 数据库增强(MyBatis-Plus、多租户)
│ ├── redis-plus-spring-boot-starter/ # Redis 缓存增强
│ ├── security-spring-boot-starter/ # 安全认证(Sa-Token)
│ ├── feign-plugin-spring-boot-starter/ # Feign 增强
│ ├── easyexcel-spring-boot-starter/ # Excel 导入导出
│ ├── diff-log-spring-boot-starter/ # 差异日志
│ ├── i18n-spring-boot-starter/ # 国际化
│ ├── pdf-spring-boot-starter/ # PDF 生成
│ ├── robot-spring-boot-starter/ # 消息机器人
│ └── websocket-spring-boot-starter/ # 分布式 WebSocket
│
├── wemirr-platform-feign/ # 🔗 Feign 接口定义
│ ├── wemirr-platform-iam-api/ # IAM 服务 API
│ ├── wemirr-platform-suite-api/ # Suite 服务 API
│ └── wemirr-platform-workflow-api/ # 工作流服务 API
│
├── wemirr-platform-iam/ # 🔐 IAM 认证授权中心
│ └── src/main/java/.../iam/
│ ├── auth/ # 认证模块
│ ├── base/ # 基础模块
│ ├── system/ # 系统管理
│ └── tenant/ # 租户管理
│
├── wemirr-platform-suite/ # 💼 业务套件中心
├── wemirr-platform-gateway/ # 🚪 API 网关
│
├── wemirr-plugin/ # 🔌 插件扩展中心
│ ├── wemirr-platform-ai/ # AI 能力(Langchain4j)
│ ├── wemirr-platform-workflow/ # 审批流程(Warm-Flow)
│ ├── wemirr-platform-monitor/ # 监控中心
│ ├── wemirr-platform-tms/ # 运输管理
│ └── wemirr-platform-wms/ # 仓储管理
│
└── 附件/ # 📁 配套资源
├── docker/ # Docker 编排
├── mysql/ # 数据库脚本
├── nacos/ # Nacos 配置
└── nginx/ # Nginx 配置模块说明
IAM 服务结构
以 wemirr-platform-iam 为例,展示服务内部结构:
wemirr-platform-iam/
└── src/main/java/com/wemirr/platform/iam/
├── IamApplication.java # 启动类
├── auth/ # 认证模块
│ ├── controller/ # 登录、Token 等接口
│ └── service/
├── base/ # 基础模块
├── system/ # 系统管理
│ ├── controller/ # 用户、角色、菜单等接口
│ │ ├── UserController.java
│ │ ├── RoleController.java
│ │ ├── ResourceController.java
│ │ ├── OrgController.java
│ │ └── PositionController.java
│ ├── domain/ # 领域模型
│ │ ├── entity/ # 实体类
│ │ ├── dto/ # 请求对象
│ │ │ ├── req/ # 请求参数
│ │ │ └── resp/ # 响应结果
│ │ └── convert/ # 对象转换
│ ├── repository/ # 数据访问层(Mapper)
│ ├── service/ # 业务逻辑
│ └── func/ # 功能函数
└── tenant/ # 租户管理框架组件说明
| 组件 | 说明 |
|---|---|
common-framework-core | 基础注解、接口、异常、工具类 |
common-spring-boot-starter | 全局异常处理、参数校验、日志等 |
db-spring-boot-starter | MyBatis-Plus 配置、多租户、数据权限 |
redis-plus-spring-boot-starter | Redis 配置、分布式锁、缓存注解 |
security-spring-boot-starter | Sa-Token 配置、权限注解 |
feign-plugin-spring-boot-starter | Feign 增强、Token 传递、请求头复制 |
easyexcel-spring-boot-starter | Excel 导入导出,支持注解方式 |
diff-log-spring-boot-starter | 变更日志记录 |
robot-spring-boot-starter | 钉钉、企微、飞书机器人消息 |
websocket-spring-boot-starter | 分布式 WebSocket 消息推送 |
快速开始
1. 创建新服务
bash
# 在 wemirr-platform 目录下创建新模块
mkdir -p wemirr-platform-demo/{demo-api,demo-biz,demo-server}2. 配置 pom.xml
xml
<!-- demo-server/pom.xml -->
<parent>
<groupId>com.wemirr.platform</groupId>
<artifactId>wemirr-platform-demo</artifactId>
<version>${revision}</version>
</parent>
<artifactId>demo-server</artifactId>
<dependencies>
<dependency>
<groupId>com.wemirr.platform</groupId>
<artifactId>demo-biz</artifactId>
</dependency>
<dependency>
<groupId>com.wemirr.framework</groupId>
<artifactId>common-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.wemirr.framework</groupId>
<artifactId>db-spring-boot-starter</artifactId>
</dependency>
</dependencies>3. 创建启动类
java
@SpringBootApplication
@EnableDiscoveryClient
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}4. 配置文件
yaml
# bootstrap.yml
spring:
application:
name: wemirr-platform-demo
profiles:
active: dev
cloud:
nacos:
discovery:
server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
namespace: ${NACOS_NAMESPACE:v4-dev}
config:
server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
namespace: ${NACOS_NAMESPACE:v4-dev}
file-extension: yml
shared-configs:
- data-id: common.yml
group: DEFAULT_GROUP
refresh: true开发规范
代码分层
Controller → 接收请求、参数校验、调用 Service
↓
Service → 业务逻辑、事务管理、调用 Mapper
↓
Mapper → 数据访问、SQL 操作
↓
Entity → 数据库实体映射命名规范
| 类型 | 规范 | 示例 |
|---|---|---|
| 类名 | PascalCase | UserController |
| 方法名 | camelCase | getUserById |
| 变量名 | camelCase | userName |
| 常量 | UPPER_SNAKE_CASE | MAX_RETRY_COUNT |
| 包名 | 全小写 | com.wemirr.iam |
接口规范
java
// RESTful 风格
GET /users // 列表查询
GET /users/{id} // 单个查询
POST /users // 新增
PUT /users/{id} // 更新
DELETE /users/{id} // 删除
// 统一响应格式
{
"code": 0, // 状态码,0 成功,其他失败
"message": "success",// 消息
"data": {} // 数据
}异常处理
java
// 使用统一业务异常
throw new BizException("用户不存在");
throw new BizException(ResultCode.USER_NOT_FOUND);
// 异常枚举
public enum ResultCode {
SUCCESS(0, "成功"),
USER_NOT_FOUND(10001, "用户不存在"),
PARAM_ERROR(10002, "参数错误"),
// ...
}核心功能(真实代码示例)
以下代码均来自 wemirr-platform-iam 模块,是项目中的真实实现。
Controller 示例
java
// 来自 UserController.java
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/users")
@Tag(name = "用户管理", description = "用户管理")
public class UserController {
private final UserService userService;
@PostMapping("/page")
@Operation(summary = "用户列表")
@SaCheckPermission(value = {"sys:user:page"})
public IPage<UserPageResp> pageList(@RequestBody UserPageReq req) {
return this.userService.pageList(req);
}
@PostMapping("/create")
@AccessLog(module = "用户管理", description = "添加用户")
@Operation(summary = "添加用户")
@SaCheckPermission(value = {"sys:user:add"})
public void create(@Validated @RequestBody UserSaveReq req) {
this.userService.create(req);
}
@PutMapping("/{id}")
@AccessLog(module = "用户管理", description = "编辑用户")
@SaCheckPermission(value = {"sys:user:edit"})
public void modify(@PathVariable Long id, @Validated @RequestBody UserUpdateReq req) {
this.userService.modify(id, req);
}
@DeleteMapping("/{id}")
@AccessLog(module = "用户管理", description = "删除用户")
@SaCheckPermission(value = {"sys:user:remove"})
public void del(@PathVariable Long id) {
this.userService.delete(id);
}
@PostMapping("/export")
@SaCheckPermission(value = {"sys:user:export"})
@ResponseExcel(fileName = "用户列表") // 一键导出 Excel
public List<UserPageResp> exportList(@RequestBody UserPageReq req) {
req.setCurrent(1);
req.setSize(-1);
return this.userService.pageList(req).getRecords();
}
}Service 示例
java
// 来自 UserServiceImpl.java
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends SuperServiceImpl<UserMapper, User> implements UserService {
private final AuthenticationContext context;
@Override
public void create(UserSaveReq req) {
// 检查账号是否存在
final long count = super.count(Wraps.<User>lbQ().eq(User::getUsername, req.getUsername()));
if (count > 0) {
throw CheckedException.badRequest("账号已存在");
}
var bean = BeanUtil.toBean(req, User.class);
bean.setPassword(PasswordEncoderHelper.encode(req.getPassword()));
bean.setTenantId(context.tenantId()); // 自动设置租户ID
this.baseMapper.insert(bean);
}
@Override
@DiffLog(group = "用户管理", tag = "编辑用户", businessKey = "{{#id}}",
success = "更新用户信息 {_DIFF{#_newObj}}",
fail = "更新用户信息异常 {{#id}}")
public void modify(Long id, UserUpdateReq req) {
User oldUser = Optional.ofNullable(this.baseMapper.selectById(id))
.orElseThrow(() -> CheckedException.notFound("用户不存在"));
User newUser = BeanUtilPlus.toBean(id, req, User.class);
DiffLogContext.putDiffItem(oldUser, newUser); // 差异日志
this.baseMapper.updateById(newUser);
}
@Override
@DSTransactional(rollbackFor = Exception.class) // 分布式事务
public void delete(Long id) {
final User user = Optional.ofNullable(getById(id))
.orElseThrow(() -> CheckedException.notFound("用户不存在"));
if (user.getReadonly()) {
throw CheckedException.badRequest("内置用户不允许删除");
}
baseMapper.deleteById(id);
userRoleMapper.delete(Wraps.<UserRole>lbQ().eq(UserRole::getUserId, id));
}
}请求参数校验
java
// 来自 UserSaveReq.java - 新增用户请求
@Data
@Schema(name = "UserSaveReq", description = "保存用户信息实体")
public class UserSaveReq {
@Schema(description = "账号")
@NotBlank(message = "账号不能为空")
@Length(max = 30, message = "账号长度不能超过{max}")
private String username;
@Schema(description = "密码")
@NotBlank(message = "密码不能为空")
@Length(max = 64, message = "密码长度不能超过{max}")
private String password;
@Schema(description = "姓名")
@NotBlank(message = "姓名不能为空")
@Length(max = 50, message = "姓名长度不能超过50")
private String nickName;
@Schema(description = "手机")
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = RegexPool.MOBILE, message = "手机号格式错误")
private String mobile;
@NotNull(message = "性别不能为空")
private Sex sex;
}
// 来自 UserPageReq.java - 分页查询请求
@Data
@EqualsAndHashCode(callSuper = true)
public class UserPageReq extends PageRequest {
@Schema(description = "用户名")
private String username;
@Schema(description = "昵称")
private String nickName;
@Schema(description = "状态")
private Boolean status;
@Schema(description = "组织ID")
private Long orgId;
}实体类定义
java
// 来自 User.java
@Data
@SuperBuilder
@NoArgsConstructor
@TableName("t_user")
@Schema(name = "User", description = "用户")
public class User extends SuperEntity<Long> {
@Schema(description = "用户名")
@DiffField(name = "用户名", strategy = DiffFieldStrategy.NOT_NULL) // 差异对比字段
private String username;
@Schema(description = "租户ID")
private Long tenantId;
@Schema(description = "昵称")
@DiffField(name = "昵称")
private String nickName;
@Schema(description = "手机号")
@DiffField(name = "手机号")
private String mobile;
@Schema(description = "是否只读")
private Boolean readonly;
@Schema(description = "状态(false=禁用;true=启用)")
private Boolean status;
@Schema(description = "机构ID")
private Long orgId;
}异常处理
java
// 框架提供的 CheckedException
// 400 错误
throw CheckedException.badRequest("账号已存在");
throw CheckedException.badRequest("订单 {0} 不存在", orderId);
// 404 错误
throw CheckedException.notFound("用户不存在");
// 403 错误
throw CheckedException.forbidden("登录过期,请重新登录");框架特有功能
常用注解速查
| 注解 | 所属模块 | 说明 |
|---|---|---|
@SaCheckPermission | Sa-Token | 权限校验 |
@AccessLog | common-framework | 操作日志记录 |
@DiffLog | diff-log-starter | 差异日志(自动记录修改前后对比) |
@DiffField | diff-log-starter | 标记需要对比的字段 |
@ResponseExcel | easyexcel-starter | 一键导出 Excel |
@TenantIgnore | db-starter | 忽略租户过滤 |
@DSTransactional | db-starter | 分布式事务 |
@RemoteResult | feign-starter | 远程结果自动填充 |
常用工具类
| 类名 | 说明 |
|---|---|
AuthenticationContext | 获取当前登录用户信息 |
CheckedException | 统一业务异常 |
BeanUtilPlus | Bean 转换增强 |
Wraps | MyBatis-Plus 条件构造器简化 |
TenantHelper | 租户切换辅助 |
PasswordEncoderHelper | 密码加密 |
获取当前用户
java
@RequiredArgsConstructor
public class DemoService {
private final AuthenticationContext context;
public void demo() {
Long userId = context.userId(); // 用户ID
Long tenantId = context.tenantId(); // 租户ID
String username = context.username(); // 用户名
String nickname = context.nickname(); // 昵称
List<String> roles = context.roles(); // 角色列表
}
}条件构造器简化
java
// 使用 Wraps 简化 LambdaQueryWrapper 创建
Wraps.<User>lbQ()
.eq(User::getUsername, req.getUsername())
.like(User::getNickName, req.getNickName())
.eq(User::getStatus, req.getStatus())
.in(User::getOrgId, orgIds);租户切换
java
// 临时忽略租户过滤
TenantHelper.withIgnoreStrategy(() -> {
return userMapper.selectById(userId);
});
// 切换到指定租户执行
TenantHelper.executeWithTenantDb("8888", () -> {
return orderMapper.selectList(null);
});