package cn.yinlihupo.service.system.impl; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.yinlihupo.common.config.FeishuConfig; import cn.yinlihupo.domain.entity.SysUser; import cn.yinlihupo.mapper.SysUserMapper; import cn.yinlihupo.service.system.FeishuAuthService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; /** * 飞书认证服务实现类 */ @Slf4j @Service @RequiredArgsConstructor public class FeishuAuthServiceImpl implements FeishuAuthService { private final FeishuConfig feishuConfig; private final SysUserMapper sysUserMapper; /** * 飞书OAuth授权端点 */ private static final String FEISHU_OAUTH_URL = "https://open.feishu.cn/open-apis/authen/v1/authorize"; /** * 获取访问令牌端点 */ private static final String FEISHU_TOKEN_URL = "https://open.feishu.cn/open-apis/authen/v1/oidc/access_token"; /** * 获取用户信息端点 */ private static final String FEISHU_USER_INFO_URL = "https://open.feishu.cn/open-apis/authen/v1/user_info"; /** * 应用访问令牌端点(用于获取应用级token) */ private static final String FEISHU_APP_TOKEN_URL = "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal"; @Override public String buildAuthUrl(String state) { String encodedRedirectUri = URLEncoder.encode(feishuConfig.getRedirectUri(), StandardCharsets.UTF_8); return FEISHU_OAUTH_URL + "?app_id=" + feishuConfig.getAppId() + "&redirect_uri=" + encodedRedirectUri + "&state=" + (state != null ? state : "") + "&scope=contact:user.base contact:user.phone:readonly"; } @Override @Transactional(rollbackFor = Exception.class) public SysUser loginByCode(String code) { log.info("处理飞书登录, code: {}", code); // 1. 获取应用访问令牌 String appAccessToken = getAppAccessToken(); // 2. 使用授权码获取用户访问令牌 String userAccessToken = getUserAccessToken(code, appAccessToken); // 3. 获取用户信息 JSONObject userInfo = getUserInfo(userAccessToken); // 4. 提取用户信息 String phone = userInfo.getStr("mobile"); String realName = userInfo.getStr("name"); String avatar = userInfo.getStr("avatar_url"); String email = userInfo.getStr("email"); String openId = userInfo.getStr("open_id"); log.info("飞书用户信息: phone={}, name={}, openId={}", phone, realName, openId); // 5. 根据手机号获取或创建用户 return getOrCreateUserByPhone(phone, realName, avatar, email, openId); } /** * 获取应用访问令牌 */ private String getAppAccessToken() { JSONObject requestBody = new JSONObject(); requestBody.set("app_id", feishuConfig.getAppId()); requestBody.set("app_secret", feishuConfig.getAppSecret()); try (HttpResponse response = HttpRequest.post(FEISHU_APP_TOKEN_URL) .header("Content-Type", "application/json") .body(requestBody.toString()) .execute()) { String body = response.body(); log.debug("获取应用访问令牌响应: {}", body); JSONObject jsonObject = JSONUtil.parseObj(body); if (jsonObject.getInt("code") != 0) { throw new RuntimeException("获取应用访问令牌失败: " + jsonObject.getStr("msg")); } return jsonObject.getStr("app_access_token"); } } /** * 使用授权码获取用户访问令牌 */ private String getUserAccessToken(String code, String appAccessToken) { JSONObject requestBody = new JSONObject(); requestBody.set("grant_type", "authorization_code"); requestBody.set("code", code); try (HttpResponse response = HttpRequest.post(FEISHU_TOKEN_URL) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + appAccessToken) .body(requestBody.toString()) .execute()) { String body = response.body(); log.debug("获取用户访问令牌响应: {}", body); JSONObject jsonObject = JSONUtil.parseObj(body); if (jsonObject.getInt("code") != 0) { throw new RuntimeException("获取用户访问令牌失败: " + jsonObject.getStr("msg")); } return jsonObject.getJSONObject("data").getStr("access_token"); } } /** * 获取用户信息 */ private JSONObject getUserInfo(String userAccessToken) { try (HttpResponse response = HttpRequest.get(FEISHU_USER_INFO_URL) .header("Authorization", "Bearer " + userAccessToken) .execute()) { String body = response.body(); log.debug("获取用户信息响应: {}", body); JSONObject jsonObject = JSONUtil.parseObj(body); if (jsonObject.getInt("code") != 0) { throw new RuntimeException("获取用户信息失败: " + jsonObject.getStr("msg")); } return jsonObject.getJSONObject("data"); } } @Override @Transactional(rollbackFor = Exception.class) public SysUser getOrCreateUserByPhone(String phone, String realName, String avatar, String email, String openId) { // 根据手机号查询用户 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SysUser::getPhone, phone); SysUser user = sysUserMapper.selectOne(queryWrapper); LocalDateTime now = LocalDateTime.now(); if (user != null) { // 用户已存在,更新信息 log.info("用户已存在,更新用户信息, phone: {}", phone); user.setRealName(realName); user.setAvatar(avatar); user.setEmail(email); user.setNickname(realName); user.setLastLoginTime(now); user.setUpdateTime(now); // 使用openId作为用户名(如果没有设置过) if (user.getUsername() == null || user.getUsername().isEmpty()) { user.setUsername(openId); } sysUserMapper.updateById(user); } else { // 用户不存在,创建新用户 log.info("用户不存在,创建新用户, phone: {}", phone); user = new SysUser(); user.setUsername(openId); user.setRealName(realName); user.setNickname(realName); user.setAvatar(avatar); user.setPhone(phone); user.setEmail(email); user.setStatus(1); // 正常状态 user.setLastLoginTime(now); user.setCreateTime(now); user.setUpdateTime(now); user.setDeleted(0); // 飞书登录用户不需要密码,设置一个随机密码 user.setPassword("FEISHU_OAUTH_USER"); sysUserMapper.insert(user); } return user; } }