Skip to content

服务开发指南

作者:唐亚峰 | 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());
    }
}

下一步