服务开发指南
作者:唐亚峰 | battcn
字数统计:1.4k 字
学习目标
掌握 Wemirr Platform 后端服务开发的核心功能和最佳实践
上下文获取
获取当前用户信息
java
import com.wemirr.framework.security.domain.AuthenticationContext;
@Service
@RequiredArgsConstructor
public class UserService {
private final AuthenticationContext context;
public void doSomething() {
// 获取当前用户ID
Long userId = context.userId();
// 获取当前租户ID
Long tenantId = context.tenantId();
// 获取当前用户名
String username = context.username();
// 获取当前用户昵称
String nickname = context.nickname();
// 获取当前用户角色
List<String> roles = context.roles();
// 获取当前用户权限
List<String> permissions = context.permissions();
}
}忽略租户过滤
java
import com.wemirr.framework.db.annotation.TenantIgnore;
// 方法级忽略
@TenantIgnore
public List<User> getAllUsers() {
return userMapper.selectList(null);
}
// 使用 TenantContextHolder 临时忽略
TenantContextHolder.setIgnore(true);
try {
// 这里的查询不会添加租户过滤
List<User> users = userMapper.selectList(null);
} finally {
TenantContextHolder.clear();
}事务管理
基本事务
java
@Transactional(rollbackFor = Exception.class)
public void createOrder(OrderDTO dto) {
// 保存订单
Order order = new Order();
BeanUtil.copyProperties(dto, order);
orderMapper.insert(order);
// 保存订单明细
List<OrderItem> items = dto.getItems().stream()
.map(item -> {
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
BeanUtil.copyProperties(item, orderItem);
return orderItem;
})
.toList();
orderItemMapper.insertBatch(items);
// 扣减库存
stockService.deduct(dto.getItems());
}事务传播机制
java
// 必须在事务中执行,没有则创建新事务
@Transactional(propagation = Propagation.REQUIRED)
// 必须在事务中执行,没有则抛出异常
@Transactional(propagation = Propagation.MANDATORY)
// 创建新事务,挂起当前事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
// 嵌套事务
@Transactional(propagation = Propagation.NESTED)编程式事务
java
@Service
@RequiredArgsConstructor
public class OrderService {
private final TransactionTemplate transactionTemplate;
public void createOrder(OrderDTO dto) {
transactionTemplate.execute(status -> {
try {
// 业务逻辑
saveOrder(dto);
return true;
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
});
}
}分布式锁
使用 Redisson
java
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
@Service
@RequiredArgsConstructor
public class StockService {
private final RedissonClient redissonClient;
public void deduct(Long productId, Integer quantity) {
String lockKey = "stock:lock:" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁,最多等待10秒,锁定30秒后自动释放
boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (!locked) {
throw new BizException("系统繁忙,请稍后重试");
}
// 扣减库存
Stock stock = stockMapper.selectByProductId(productId);
if (stock.getQuantity() < quantity) {
throw new BizException("库存不足");
}
stock.setQuantity(stock.getQuantity() - quantity);
stockMapper.updateById(stock);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BizException("获取锁被中断");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}使用注解式分布式锁
java
import com.wemirr.framework.redis.lock.annotation.RedisLock;
@Service
public class OrderService {
@RedisLock(key = "'order:create:' + #dto.userId", expire = 30)
public void createOrder(OrderDTO dto) {
// 同一用户30秒内只能创建一次订单
doCreateOrder(dto);
}
}缓存使用
Spring Cache 注解
java
@Service
public class DictService {
// 查询时缓存
@Cacheable(value = "dict", key = "#code")
public List<DictItem> getByCode(String code) {
return dictMapper.selectByCode(code);
}
// 更新时清除缓存
@CacheEvict(value = "dict", key = "#dto.code")
public void update(DictDTO dto) {
dictMapper.updateById(dto);
}
// 更新时更新缓存
@CachePut(value = "dict", key = "#dto.code")
public Dict save(DictDTO dto) {
dictMapper.insert(dto);
return dto;
}
// 清除所有缓存
@CacheEvict(value = "dict", allEntries = true)
public void clearAll() {
}
}Redis 操作
java
import org.springframework.data.redis.core.StringRedisTemplate;
@Service
@RequiredArgsConstructor
public class CacheService {
private final StringRedisTemplate redisTemplate;
// 字符串操作
public void setString(String key, String value, long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
public String getString(String key) {
return redisTemplate.opsForValue().get(key);
}
// Hash 操作
public void setHash(String key, String field, String value) {
redisTemplate.opsForHash().put(key, field, value);
}
// Set 操作
public void addToSet(String key, String... values) {
redisTemplate.opsForSet().add(key, values);
}
// List 操作
public void pushToList(String key, String value) {
redisTemplate.opsForList().rightPush(key, value);
}
// 删除
public void delete(String key) {
redisTemplate.delete(key);
}
}异步处理
@Async 异步方法
java
@Service
@Slf4j
public class NotifyService {
@Async
public void sendEmail(String to, String subject, String content) {
log.info("开始发送邮件: {}", to);
// 发送邮件逻辑
emailClient.send(to, subject, content);
log.info("邮件发送完成: {}", to);
}
@Async
public CompletableFuture<Boolean> sendSms(String mobile, String content) {
log.info("开始发送短信: {}", mobile);
boolean result = smsClient.send(mobile, content);
return CompletableFuture.completedFuture(result);
}
}使用方式
java
@Service
@RequiredArgsConstructor
public class OrderService {
private final NotifyService notifyService;
public void createOrder(OrderDTO dto) {
// 创建订单
Order order = doCreateOrder(dto);
// 异步发送通知
notifyService.sendEmail(dto.getEmail(), "订单创建成功", "...");
notifyService.sendSms(dto.getMobile(), "...");
}
}服务调用
Feign 调用
java
// 定义 Feign 客户端
@FeignClient(name = "wemirr-platform-iam", path = "/users")
public interface UserFeignClient {
@GetMapping("/{id}")
Result<UserVO> getById(@PathVariable Long id);
@GetMapping("/batch")
Result<List<UserVO>> getByIds(@RequestParam List<Long> ids);
}
// 使用
@Service
@RequiredArgsConstructor
public class OrderService {
private final UserFeignClient userFeignClient;
public OrderVO getOrderDetail(Long orderId) {
Order order = orderMapper.selectById(orderId);
OrderVO vo = BeanUtil.copyProperties(order, OrderVO.class);
// 调用用户服务获取用户信息
Result<UserVO> result = userFeignClient.getById(order.getUserId());
if (result.isSuccess()) {
vo.setUserName(result.getData().getNickname());
}
return vo;
}
}远程字段映射
使用 @Remote 注解自动填充远程数据:
java
@Data
public class OrderVO {
private Long id;
private Long userId;
@Remote(feign = UserFeignClient.class, method = "getById",
sourceField = "userId", targetField = "nickname")
private String userName;
}定时任务
使用 @Scheduled
java
@Component
@Slf4j
public class CleanTask {
// 每天凌晨2点执行
@Scheduled(cron = "0 0 2 * * ?")
public void cleanExpiredData() {
log.info("开始清理过期数据");
// 清理逻辑
}
// 每5分钟执行一次
@Scheduled(fixedRate = 5 * 60 * 1000)
public void syncData() {
log.info("同步数据");
}
}使用 Snail Job(分布式任务)
java
@Component
public class SyncOrderTask {
@JobExecutor(name = "syncOrderJob")
public ExecuteResult execute(JobArgs args) {
// 获取任务参数
String params = args.getArgsStr();
// 执行任务逻辑
doSync();
return ExecuteResult.success("同步完成");
}
}文件上传
java
@RestController
@RequestMapping("/files")
@RequiredArgsConstructor
public class FileController {
private final OssTemplate ossTemplate;
@PostMapping("/upload")
public Result<String> upload(@RequestParam MultipartFile file) {
// 上传文件
String url = ossTemplate.upload(file);
return Result.success(url);
}
@DeleteMapping
public Result<Void> delete(@RequestParam String url) {
ossTemplate.delete(url);
return Result.success();
}
}消息推送
WebSocket 推送
java
@Service
@RequiredArgsConstructor
public class MessageService {
private final WebSocketTemplate webSocketTemplate;
// 推送给指定用户
public void sendToUser(Long userId, String message) {
webSocketTemplate.sendToUser(userId.toString(), message);
}
// 广播消息
public void broadcast(String message) {
webSocketTemplate.broadcast(message);
}
}最佳实践
1. 参数校验
java
// 使用分组校验
public interface Create {}
public interface Update {}
@Data
public class UserDTO {
@Null(groups = Create.class)
@NotNull(groups = Update.class)
private Long id;
@NotBlank(groups = {Create.class, Update.class})
private String username;
}
@PostMapping
public Result<Void> create(@RequestBody @Validated(Create.class) UserDTO dto) {
// ...
}
@PutMapping("/{id}")
public Result<Void> update(@RequestBody @Validated(Update.class) UserDTO dto) {
// ...
}2. 异常处理
java
// 抛出业务异常
throw new BizException("用户不存在");
throw new BizException(ResultCode.USER_NOT_FOUND);
// 断言式异常
BizAssert.notNull(user, "用户不存在");
BizAssert.isTrue(user.getStatus() == 1, "用户已被禁用");3. 日志规范
java
@Slf4j
public class OrderService {
public void createOrder(OrderDTO dto) {
log.info("开始创建订单, userId={}, productId={}", dto.getUserId(), dto.getProductId());
try {
// 业务逻辑
log.debug("订单详情: {}", JsonUtil.toJson(dto));
} catch (Exception e) {
log.error("创建订单失败, dto={}", dto, e);
throw e;
}
log.info("订单创建成功, orderId={}", order.getId());
}
}