diff --git a/ruoyi-client/pom.xml b/ruoyi-client/pom.xml
new file mode 100644
index 0000000..3ffb319
--- /dev/null
+++ b/ruoyi-client/pom.xml
@@ -0,0 +1,34 @@
+
+
+
+ ruoyi
+ com.ruoyi
+ 3.9.0
+
+ 4.0.0
+
+ ruoyi-client
+
+
+ 用户端服务模块,支持第三方登录(微信、支付宝、抖音)
+
+
+
+
+
+
+ com.ruoyi
+ ruoyi-common
+
+
+
+
+ com.ruoyi
+ ruoyi-system
+
+
+
+
+
diff --git a/ruoyi-client/src/main/java/com/ruoyi/client/controller/ThirdPartyLoginController.java b/ruoyi-client/src/main/java/com/ruoyi/client/controller/ThirdPartyLoginController.java
new file mode 100644
index 0000000..7a282d1
--- /dev/null
+++ b/ruoyi-client/src/main/java/com/ruoyi/client/controller/ThirdPartyLoginController.java
@@ -0,0 +1,184 @@
+package com.ruoyi.client.controller;
+
+import com.ruoyi.system.domain.thirdparty.LoginRequest;
+import com.ruoyi.system.domain.thirdparty.LoginResponse;
+import com.ruoyi.system.service.thirdparty.ThirdPartyLoginService;
+import com.ruoyi.system.service.thirdparty.impl.WeChatLoginServiceImpl;
+import com.ruoyi.system.service.thirdparty.impl.AlipayLoginServiceImpl;
+import com.ruoyi.system.service.thirdparty.impl.DouyinLoginServiceImpl;
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 第三方登录Controller
+ */
+@RestController
+@RequestMapping("/client/login")
+public class ThirdPartyLoginController extends BaseController
+{
+ @Autowired
+ private WeChatLoginServiceImpl weChatLoginService;
+
+ @Autowired
+ private AlipayLoginServiceImpl alipayLoginService;
+
+ @Autowired
+ private DouyinLoginServiceImpl douyinLoginService;
+
+ /**
+ * 统一第三方登录接口
+ */
+ @Anonymous
+ @PostMapping("/thirdParty")
+ public AjaxResult thirdPartyLogin(@RequestBody LoginRequest request)
+ {
+ try
+ {
+ ThirdPartyLoginService loginService = getLoginService(request.getLoginType());
+
+ if (loginService == null)
+ {
+ return AjaxResult.error("不支持的登录类型");
+ }
+
+ LoginResponse response = loginService.login(request);
+
+ Map result = new HashMap<>();
+ result.put("userId", response.getUserId());
+ result.put("username", response.getUsername());
+ result.put("nickname", response.getNickname());
+ result.put("avatar", response.getAvatar());
+ result.put("token", response.getToken());
+ result.put("userLevel", response.getUserLevel());
+ result.put("coin", response.getCoin());
+ result.put("diamond", response.getDiamond());
+ result.put("isNewUser", response.getIsNewUser());
+
+ return AjaxResult.success(result);
+ }
+ catch (Exception e)
+ {
+ logger.error("第三方登录失败", e);
+ return AjaxResult.error("登录失败:" + e.getMessage());
+ }
+ }
+
+ /**
+ * 微信登录
+ */
+ @Anonymous
+ @PostMapping("/wechat")
+ public AjaxResult wechatLogin(@RequestBody LoginRequest request)
+ {
+ try
+ {
+ LoginResponse response = weChatLoginService.login(request);
+
+ Map result = new HashMap<>();
+ result.put("userId", response.getUserId());
+ result.put("username", response.getUsername());
+ result.put("nickname", response.getNickname());
+ result.put("avatar", response.getAvatar());
+ result.put("token", response.getToken());
+ result.put("userLevel", response.getUserLevel());
+ result.put("coin", response.getCoin());
+ result.put("diamond", response.getDiamond());
+ result.put("isNewUser", response.getIsNewUser());
+
+ return AjaxResult.success(result);
+ }
+ catch (Exception e)
+ {
+ logger.error("微信登录失败", e);
+ return AjaxResult.error("登录失败:" + e.getMessage());
+ }
+ }
+
+ /**
+ * 支付宝登录
+ */
+ @Anonymous
+ @PostMapping("/alipay")
+ public AjaxResult alipayLogin(@RequestBody LoginRequest request)
+ {
+ try
+ {
+ LoginResponse response = alipayLoginService.login(request);
+
+ Map result = new HashMap<>();
+ result.put("userId", response.getUserId());
+ result.put("username", response.getUsername());
+ result.put("nickname", response.getNickname());
+ result.put("avatar", response.getAvatar());
+ result.put("token", response.getToken());
+ result.put("userLevel", response.getUserLevel());
+ result.put("coin", response.getCoin());
+ result.put("diamond", response.getDiamond());
+ result.put("isNewUser", response.getIsNewUser());
+
+ return AjaxResult.success(result);
+ }
+ catch (Exception e)
+ {
+ logger.error("支付宝登录失败", e);
+ return AjaxResult.error("登录失败:" + e.getMessage());
+ }
+ }
+
+ /**
+ * 抖音登录
+ */
+ @Anonymous
+ @PostMapping("/douyin")
+ public AjaxResult douyinLogin(@RequestBody LoginRequest request)
+ {
+ try
+ {
+ LoginResponse response = douyinLoginService.login(request);
+
+ Map result = new HashMap<>();
+ result.put("userId", response.getUserId());
+ result.put("username", response.getUsername());
+ result.put("nickname", response.getNickname());
+ result.put("avatar", response.getAvatar());
+ result.put("token", response.getToken());
+ result.put("userLevel", response.getUserLevel());
+ result.put("coin", response.getCoin());
+ result.put("diamond", response.getDiamond());
+ result.put("isNewUser", response.getIsNewUser());
+
+ return AjaxResult.success(result);
+ }
+ catch (Exception e)
+ {
+ logger.error("抖音登录失败", e);
+ return AjaxResult.error("登录失败:" + e.getMessage());
+ }
+ }
+
+ /**
+ * 根据登录类型获取对应的服务
+ */
+ private ThirdPartyLoginService getLoginService(String loginType)
+ {
+ if ("wechat".equalsIgnoreCase(loginType))
+ {
+ return weChatLoginService;
+ }
+ else if ("alipay".equalsIgnoreCase(loginType))
+ {
+ return alipayLoginService;
+ }
+ else if ("douyin".equalsIgnoreCase(loginType))
+ {
+ return douyinLoginService;
+ }
+ return null;
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/config/WxMaConfiguration.java b/ruoyi-system/src/main/java/com/ruoyi/system/config/WxMaConfiguration.java
new file mode 100644
index 0000000..ad3b64e
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/config/WxMaConfiguration.java
@@ -0,0 +1,34 @@
+package com.ruoyi.system.config;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
+import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 微信小程序配置
+ */
+@Configuration
+public class WxMaConfiguration
+{
+ @Value("${wx.miniapp.appid}")
+ private String appId;
+
+ @Value("${wx.miniapp.secret}")
+ private String secret;
+
+ @Bean
+ public WxMaService wxMaService()
+ {
+ WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
+ config.setAppid(appId);
+ config.setSecret(secret);
+
+ WxMaServiceImpl service = new WxMaServiceImpl();
+ service.setWxMaConfig(config);
+
+ return service;
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IFateSkillsService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IFateSkillsService.java
new file mode 100644
index 0000000..8c92333
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IFateSkillsService.java
@@ -0,0 +1,62 @@
+package com.ruoyi.system.service;
+
+import com.ruoyi.system.domain.FateSkills;
+
+import java.util.List;
+
+/**
+ * 技能基础Service接口
+ *
+ * @author ruoyi
+ * @date 2025-01-18
+ */
+public interface IFateSkillsService
+{
+ /**
+ * 查询技能基础
+ *
+ * @param skillId 技能基础主键
+ * @return 技能基础
+ */
+ public FateSkills selectFateSkillsBySkillId(Long skillId);
+
+ /**
+ * 查询技能基础列表
+ *
+ * @param fateSkills 技能基础
+ * @return 技能基础集合
+ */
+ public List selectFateSkillsList(FateSkills fateSkills);
+
+ /**
+ * 新增技能基础
+ *
+ * @param fateSkills 技能基础
+ * @return 结果
+ */
+ public int insertFateSkills(FateSkills fateSkills);
+
+ /**
+ * 修改技能基础
+ *
+ * @param fateSkills 技能基础
+ * @return 结果
+ */
+ public int updateFateSkills(FateSkills fateSkills);
+
+ /**
+ * 批量删除技能基础
+ *
+ * @param skillIds 需要删除的技能基础主键集合
+ * @return 结果
+ */
+ public int deleteFateSkillsBySkillIds(Long[] skillIds);
+
+ /**
+ * 删除技能基础信息
+ *
+ * @param skillId 技能基础主键
+ * @return 结果
+ */
+ public int deleteFateSkillsBySkillId(Long skillId);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IFateUserInfoService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IFateUserInfoService.java
new file mode 100644
index 0000000..af0bada
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IFateUserInfoService.java
@@ -0,0 +1,61 @@
+package com.ruoyi.system.service;
+
+import com.ruoyi.system.domain.FateUserInfo;
+import java.util.List;
+
+/**
+ * 用户信息Service接口
+ *
+ * @author ruoyi
+ * @date 2025-01-18
+ */
+public interface IFateUserInfoService
+{
+ /**
+ * 查询用户信息
+ *
+ * @param userId 用户信息主键
+ * @return 用户信息
+ */
+ public FateUserInfo selectFateUserInfoByUserId(Long userId);
+
+ /**
+ * 查询用户信息列表
+ *
+ * @param fateUserInfo 用户信息
+ * @return 用户信息集合
+ */
+ public List selectFateUserInfoList(FateUserInfo fateUserInfo);
+
+ /**
+ * 新增用户信息
+ *
+ * @param fateUserInfo 用户信息
+ * @return 结果
+ */
+ public int insertFateUserInfo(FateUserInfo fateUserInfo);
+
+ /**
+ * 修改用户信息
+ *
+ * @param fateUserInfo 用户信息
+ * @return 结果
+ */
+ public int updateFateUserInfo(FateUserInfo fateUserInfo);
+
+ /**
+ * 批量删除用户信息
+ *
+ * @param userIds 需要删除的用户信息主键集合
+ * @return 结果
+ */
+ public int deleteFateUserInfoByUserIds(Long[] userIds);
+
+ /**
+ * 删除用户信息信息
+ *
+ * @param userId 用户信息主键
+ * @return 结果
+ */
+ public int deleteFateUserInfoByUserId(Long userIdId);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/FateSkillsServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/FateSkillsServiceImpl.java
new file mode 100644
index 0000000..dd76834
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/FateSkillsServiceImpl.java
@@ -0,0 +1,102 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+
+import com.ruoyi.system.domain.FateSkills;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.system.mapper.FateSkillsMapper;
+import com.ruoyi.system.service.IFateSkillsService;
+
+/**
+ * 技能基础Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2025-01-18
+ */
+@Service
+public class FateSkillsServiceImpl implements IFateSkillsService
+{
+ @Autowired
+ private FateSkillsMapper fateSkillsMapper;
+
+ /**
+ * 查询技能基础
+ *
+ * @param skillId 技能基础主键
+ * @return 技能基础
+ */
+ @Override
+ public FateSkills selectFateSkillsBySkillId(Long skillId)
+ {
+ FateSkills fateSkills = fateSkillsMapper.selectFateSkillsBySkillId(skillId);
+ if (fateSkills != null) {
+ fateSkills.setSkillTypeDesc(fateSkills.getSkillTypeDesc());
+ }
+ return fateSkills;
+ }
+
+ /**
+ * 查询技能基础列表
+ *
+ * @param fateSkills 技能基础
+ * @return 技能基础
+ */
+ @Override
+ public List selectFateSkillsList(FateSkills fateSkills)
+ {
+ List list = fateSkillsMapper.selectFateSkillsList(fateSkills);
+ for (FateSkills skill : list) {
+ skill.setSkillTypeDesc(skill.getSkillTypeDesc());
+ }
+ return list;
+ }
+
+ /**
+ * 新增技能基础
+ *
+ * @param fateSkills 技能基础
+ * @return 结果
+ */
+ @Override
+ public int insertFateSkills(FateSkills fateSkills)
+ {
+ return fateSkillsMapper.insertFateSkills(fateSkills);
+ }
+
+ /**
+ * 修改技能基础
+ *
+ * @param fateSkills 技能基础
+ * @return 结果
+ */
+ @Override
+ public int updateFateSkills(FateSkills fateSkills)
+ {
+ return fateSkillsMapper.updateFateSkills(fateSkills);
+ }
+
+ /**
+ * 批量删除技能基础
+ *
+ * @param skillIds 需要删除的技能基础主键
+ * @return 结果
+ */
+ @Override
+ public int deleteFateSkillsBySkillIds(Long[] skillIds)
+ {
+ return fateSkillsMapper.deleteFateSkillsBySkillIds(skillIds);
+ }
+
+ /**
+ * 删除技能基础信息
+ *
+ * @param skillId 技能基础主键
+ * @return 结果
+ */
+ @Override
+ public int deleteFateSkillsBySkillId(Long skillId)
+ {
+ return fateSkillsMapper.deleteFateSkillsBySkillId(skillId);
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/FateUserInfoServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/FateUserInfoServiceImpl.java
new file mode 100644
index 0000000..397be90
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/FateUserInfoServiceImpl.java
@@ -0,0 +1,104 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+
+import com.ruoyi.system.domain.FateUserInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.system.mapper.FateUserInfoMapper;
+import com.ruoyi.system.service.IFateUserInfoService;
+
+/**
+ * 用户信息Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2025-01-18
+ */
+@Service
+public class FateUserInfoServiceImpl implements IFateUserInfoService
+{
+ @Autowired
+ private FateUserInfoMapper fateUserInfoMapper;
+
+ /**
+ * 查询用户信息
+ *
+ * @param userId 用户信息主键
+ * @return 用户信息
+ */
+ @Override
+ public FateUserInfo selectFateUserInfoByUserId(Long userId)
+ {
+ FateUserInfo fateUserInfo = fateUserInfoMapper.selectFateUserInfoByUserId(userId);
+ if (fateUserInfo != null) {
+ fateUserInfo.setGenderDesc(fateUserInfo.getGenderDesc());
+ fateUserInfo.setStatusDesc(fateUserInfo.getStatusDesc());
+ }
+ return fateUserInfo;
+ }
+
+ /**
+ * 查询用户信息列表
+ *
+ * @param fateUserInfo 用户信息
+ * @return 用户信息
+ */
+ @Override
+ public List selectFateUserInfoList(FateUserInfo fateUserInfo)
+ {
+ List list = fateUserInfoMapper.selectFateUserInfoList(fateUserInfo);
+ for (FateUserInfo userInfo : list) {
+ userInfo.setGenderDesc(userInfo.getGenderDesc());
+ userInfo.setStatusDesc(userInfo.getStatusDesc());
+ }
+ return list;
+ }
+
+ /**
+ * 新增用户信息
+ *
+ * @param fateUserInfo 用户信息
+ * @return 结果
+ */
+ @Override
+ public int insertFateUserInfo(FateUserInfo fateUserInfo)
+ {
+ return fateUserInfoMapper.insertFateUserInfo(fateUserInfo);
+ }
+
+ /**
+ * 修改用户信息
+ *
+ * @param fateUserInfo 用户信息
+ * @return 结果
+ */
+ @Override
+ public int updateFateUserInfo(FateUserInfo fateUserInfo)
+ {
+ return fateUserInfoMapper.updateFateUserInfo(fateUserInfo);
+ }
+
+ /**
+ * 批量删除用户信息
+ *
+ * @param userIds 需要删除的用户信息主键
+ * @return 结果
+ */
+ @Override
+ public int deleteFateUserInfoByUserIds(Long[] userIds)
+ {
+ return fateUserInfoMapper.deleteFateUserInfoByUserIds(userIds);
+ }
+
+ /**
+ * 删除用户信息信息
+ *
+ * @param userId 用户信息主键
+ * @return 结果
+ */
+ @Override
+ public int deleteFateUserInfoByUserId(Long userIdId)
+ {
+ return fateUserInfoMapper.deleteFateUserInfoByUserId(userIdId);
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/thirdparty/ThirdPartyLoginService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/thirdparty/ThirdPartyLoginService.java
new file mode 100644
index 0000000..5b4e679
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/thirdparty/ThirdPartyLoginService.java
@@ -0,0 +1,18 @@
+package com.ruoyi.system.service.thirdparty;
+
+import com.ruoyi.system.domain.thirdparty.LoginRequest;
+import com.ruoyi.system.domain.thirdparty.LoginResponse;
+
+/**
+ * 第三方登录服务接口
+ */
+public interface ThirdPartyLoginService
+{
+ /**
+ * 第三方登录
+ *
+ * @param request 登录请求
+ * @return 登录响应
+ */
+ LoginResponse login(LoginRequest request);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/thirdparty/impl/AlipayLoginServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/thirdparty/impl/AlipayLoginServiceImpl.java
new file mode 100644
index 0000000..1d42224
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/thirdparty/impl/AlipayLoginServiceImpl.java
@@ -0,0 +1,193 @@
+package com.ruoyi.system.service.thirdparty.impl;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.request.AlipaySystemOauthTokenRequest;
+import com.alipay.api.request.AlipayUserInfoShareRequest;
+import com.alipay.api.response.AlipaySystemOauthTokenResponse;
+import com.alipay.api.response.AlipayUserInfoShareResponse;
+import com.ruoyi.system.domain.thirdparty.LoginRequest;
+import com.ruoyi.system.domain.thirdparty.LoginResponse;
+import com.ruoyi.system.service.thirdparty.ThirdPartyLoginService;
+import com.ruoyi.system.domain.FateUserAlipay;
+import com.ruoyi.system.mapper.FateUserAlipayMapper;
+import com.ruoyi.system.domain.FateUserInfo;
+import com.ruoyi.system.mapper.FateUserInfoMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+
+/**
+ * 支付宝登录服务实现
+ */
+@Service
+public class AlipayLoginServiceImpl implements ThirdPartyLoginService
+{
+ @Value("${alipay.app-id}")
+ private String appId;
+
+ @Value("${alipay.private-key}")
+ private String privateKey;
+
+ @Value("${alipay.alipay-public-key}")
+ private String alipayPublicKey;
+
+ @Value("${alipay.server-url}")
+ private String serverUrl;
+
+ @Value("${alipay.charset}")
+ private String charset;
+
+ @Value("${alipay.sign-type}")
+ private String signType;
+
+ @Autowired
+ private FateUserAlipayMapper fateUserAlipayMapper;
+
+ @Autowired
+ private FateUserInfoMapper fateUserInfoMapper;
+
+ @Override
+ public LoginResponse login(LoginRequest request)
+ {
+ LoginResponse response = new LoginResponse();
+
+ try
+ {
+ // 1. 创建支付宝客户端
+ AlipayClient alipayClient = new DefaultAlipayClient(
+ serverUrl,
+ appId,
+ privateKey,
+ "json",
+ charset,
+ alipayPublicKey,
+ signType
+ );
+
+ // 2. 通过auth_code获取access_token和user_id
+ AlipaySystemOauthTokenRequest oauthTokenRequest = new AlipaySystemOauthTokenRequest();
+ oauthTokenRequest.setGrantType("authorization_code");
+ oauthTokenRequest.setCode(request.getCode());
+ oauthTokenRequest.setRefreshToken("201208134b203fe6c014");
+
+ AlipaySystemOauthTokenResponse oauthTokenResponse = alipayClient.execute(oauthTokenRequest);
+
+ if (!oauthTokenResponse.isSuccess())
+ {
+ throw new RuntimeException("支付宝授权失败:" + oauthTokenResponse.getSubMsg());
+ }
+
+ String accessToken = oauthTokenResponse.getAccessToken();
+ String alipayUserId = oauthTokenResponse.getUserId();
+
+ // 3. 查询是否已绑定支付宝
+ FateUserAlipay alipayInfo = fateUserAlipayMapper.selectByAlipayUserId(alipayUserId);
+
+ FateUserInfo userInfo;
+ boolean isNewUser = false;
+
+ if (alipayInfo == null)
+ {
+ // 4. 新用户,创建用户信息
+ userInfo = new FateUserInfo();
+ userInfo.setUsername("alipay_" + alipayUserId.substring(0, 8));
+ userInfo.setNickname("支付宝用户");
+ userInfo.setGender(0);
+ userInfo.setUserLevel(1);
+ userInfo.setUserExp(0L);
+ userInfo.setCoin(1000L);
+ userInfo.setDiamond(100L);
+ userInfo.setStatus(1);
+ userInfo.setCreatedAt(new Date());
+ userInfo.setUpdatedAt(new Date());
+
+ // 插入用户信息
+ fateUserInfoMapper.insertFateUserInfo(userInfo);
+ isNewUser = true;
+
+ // 5. 创建支付宝绑定信息
+ FateUserAlipay newAlipayInfo = new FateUserAlipay();
+ newAlipayInfo.setUserId(userInfo.getUserId());
+ newAlipayInfo.setAlipayUserId(alipayUserId);
+ newAlipayInfo.setAccessToken(accessToken);
+ newAlipayInfo.setExpiresIn(Integer.valueOf(oauthTokenResponse.getExpiresIn()));
+ newAlipayInfo.setGender("未知");
+ newAlipayInfo.setCreatedAt(new Date());
+ newAlipayInfo.setUpdatedAt(new Date());
+ fateUserAlipayMapper.insertFateUserAlipay(newAlipayInfo);
+
+ alipayInfo = newAlipayInfo;
+ }
+ else
+ {
+ // 6. 已存在用户,更新access_token
+ alipayInfo.setAccessToken(accessToken);
+ alipayInfo.setExpiresIn(Integer.valueOf(oauthTokenResponse.getExpiresIn()));
+ alipayInfo.setUpdatedAt(new Date());
+ fateUserAlipayMapper.updateFateUserAlipay(alipayInfo);
+
+ userInfo = fateUserInfoMapper.selectFateUserInfoByUserId(alipayInfo.getUserId());
+ }
+
+ // 7. 获取支付宝用户信息
+ AlipayUserInfoShareRequest userInfoRequest = new AlipayUserInfoShareRequest();
+ AlipayUserInfoShareResponse userInfoResponse = alipayClient.execute(userInfoRequest, accessToken);
+
+ if (userInfoResponse.isSuccess())
+ {
+ // 更新支付宝信息
+ alipayInfo.setNickname(userInfoResponse.getNickName());
+ alipayInfo.setAvatar(userInfoResponse.getAvatar());
+ alipayInfo.setGender(userInfoResponse.getGender());
+ alipayInfo.setProvince(userInfoResponse.getProvince());
+ alipayInfo.setCity(userInfoResponse.getCity());
+ alipayInfo.setUpdatedAt(new Date());
+ fateUserAlipayMapper.updateFateUserAlipay(alipayInfo);
+
+ // 更新用户基本信息
+ userInfo.setNickname(userInfoResponse.getNickName());
+ userInfo.setAvatar(userInfoResponse.getAvatar());
+ if (userInfoResponse.getProvince() != null)
+ {
+ userInfo.setProvince(userInfoResponse.getProvince());
+ }
+ if (userInfoResponse.getCity() != null)
+ {
+ userInfo.setCity(userInfoResponse.getCity());
+ }
+ fateUserInfoMapper.updateFateUserInfo(userInfo);
+ }
+
+ // 8. 构建响应
+ response.setUserId(userInfo.getUserId());
+ response.setUsername(userInfo.getUsername());
+ response.setNickname(userInfo.getNickname());
+ response.setAvatar(userInfo.getAvatar());
+ response.setToken(generateToken(userInfo.getUserId()));
+ response.setUserLevel(userInfo.getUserLevel());
+ response.setCoin(userInfo.getCoin());
+ response.setDiamond(userInfo.getDiamond());
+ response.setIsNewUser(isNewUser);
+
+ return response;
+ }
+ catch (AlipayApiException e)
+ {
+ e.printStackTrace();
+ throw new RuntimeException("支付宝登录失败:" + e.getErrMsg());
+ }
+ }
+
+ /**
+ * 生成令牌
+ */
+ private String generateToken(Long userId)
+ {
+ // TODO: 实现JWT或自定义token生成逻辑
+ return "token_" + userId + "_" + System.currentTimeMillis();
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/thirdparty/impl/DouyinLoginServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/thirdparty/impl/DouyinLoginServiceImpl.java
new file mode 100644
index 0000000..2849381
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/thirdparty/impl/DouyinLoginServiceImpl.java
@@ -0,0 +1,196 @@
+package com.ruoyi.system.service.thirdparty.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.ruoyi.system.domain.thirdparty.LoginRequest;
+import com.ruoyi.system.domain.thirdparty.LoginResponse;
+import com.ruoyi.system.service.thirdparty.ThirdPartyLoginService;
+import com.ruoyi.system.domain.FateUserDouyin;
+import com.ruoyi.system.mapper.FateUserDouyinMapper;
+import com.ruoyi.system.domain.FateUserInfo;
+import com.ruoyi.system.mapper.FateUserInfoMapper;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+
+/**
+ * 抖音登录服务实现
+ */
+@Service
+public class DouyinLoginServiceImpl implements ThirdPartyLoginService
+{
+ @Value("${douyin.app-id}")
+ private String appId;
+
+ @Value("${douyin.app-secret}")
+ private String appSecret;
+
+ @Value("${douyin.access-token-url}")
+ private String accessTokenUrl;
+
+ @Value("${douyin.user-info-url}")
+ private String userInfoUrl;
+
+ @Autowired
+ private FateUserDouyinMapper fateUserDouyinMapper;
+
+ @Autowired
+ private FateUserInfoMapper fateUserInfoMapper;
+
+ @Override
+ public LoginResponse login(LoginRequest request)
+ {
+ LoginResponse response = new LoginResponse();
+
+ try
+ {
+ // 1. 通过code获取access_token
+ String tokenUrl = accessTokenUrl + "?client_key=" + appId +
+ "&client_secret=" + appSecret + "&code=" + request.getCode() +
+ "&grant_type=authorization_code";
+
+ String tokenResponse = httpPost(tokenUrl, "");
+ JSONObject tokenJson = JSON.parseObject(tokenResponse);
+
+ if (tokenJson.containsKey("error"))
+ {
+ throw new RuntimeException("抖音授权失败:" + tokenJson.getString("error_description"));
+ }
+
+ String accessToken = tokenJson.getString("access_token");
+ String openid = tokenJson.getString("open_id");
+ String unionid = tokenJson.getString("union_id");
+
+ // 2. 查询是否已绑定抖音
+ FateUserDouyin douyinInfo = fateUserDouyinMapper.selectByOpenid(openid);
+
+ FateUserInfo userInfo;
+ boolean isNewUser = false;
+
+ if (douyinInfo == null)
+ {
+ // 3. 新用户,创建用户信息
+ userInfo = new FateUserInfo();
+ userInfo.setUsername("douyin_" + openid.substring(0, 8));
+ userInfo.setNickname("抖音用户");
+ userInfo.setGender(0);
+ userInfo.setUserLevel(1);
+ userInfo.setUserExp(0L);
+ userInfo.setCoin(1000L);
+ userInfo.setDiamond(100L);
+ userInfo.setStatus(1);
+ userInfo.setCreatedAt(new Date());
+ userInfo.setUpdatedAt(new Date());
+
+ // 插入用户信息
+ fateUserInfoMapper.insertFateUserInfo(userInfo);
+ isNewUser = true;
+
+ // 4. 创建抖音绑定信息
+ FateUserDouyin newDouyinInfo = new FateUserDouyin();
+ newDouyinInfo.setUserId(userInfo.getUserId());
+ newDouyinInfo.setOpenid(openid);
+ newDouyinInfo.setUnionid(unionid);
+ newDouyinInfo.setAccessToken(accessToken);
+ newDouyinInfo.setExpiresIn(tokenJson.getInteger("expires_in"));
+ newDouyinInfo.setGender(0);
+ newDouyinInfo.setCreatedAt(new Date());
+ newDouyinInfo.setUpdatedAt(new Date());
+ fateUserDouyinMapper.insertFateUserDouyin(newDouyinInfo);
+
+ douyinInfo = newDouyinInfo;
+ }
+ else
+ {
+ // 5. 已存在用户,更新access_token
+ douyinInfo.setAccessToken(accessToken);
+ douyinInfo.setExpiresIn(tokenJson.getInteger("expires_in"));
+ douyinInfo.setUpdatedAt(new Date());
+ fateUserDouyinMapper.updateFateUserDouyin(douyinInfo);
+
+ userInfo = fateUserInfoMapper.selectFateUserInfoByUserId(douyinInfo.getUserId());
+ }
+
+ // 6. 获取抖音用户信息
+ String infoUrl = userInfoUrl + "?access_token=" + accessToken + "&open_id=" + openid;
+ String infoResponse = httpPost(infoUrl, "");
+ JSONObject infoJson = JSON.parseObject(infoResponse);
+
+ if (infoJson != null && infoJson.containsKey("data"))
+ {
+ JSONObject data = infoJson.getJSONObject("data");
+
+ // 更新抖音信息
+ douyinInfo.setNickname(data.getString("nickname"));
+ douyinInfo.setAvatar(data.getString("avatar"));
+ douyinInfo.setGender(data.getInteger("gender"));
+ douyinInfo.setCountry(data.getString("country"));
+ douyinInfo.setProvince(data.getString("province"));
+ douyinInfo.setCity(data.getString("city"));
+ douyinInfo.setUpdatedAt(new Date());
+ fateUserDouyinMapper.updateFateUserDouyin(douyinInfo);
+
+ // 更新用户基本信息
+ userInfo.setNickname(data.getString("nickname"));
+ userInfo.setAvatar(data.getString("avatar"));
+ userInfo.setGender(data.getInteger("gender"));
+ if (data.getString("province") != null)
+ {
+ userInfo.setProvince(data.getString("province"));
+ }
+ if (data.getString("city") != null)
+ {
+ userInfo.setCity(data.getString("city"));
+ }
+ fateUserInfoMapper.updateFateUserInfo(userInfo);
+ }
+
+ // 7. 构建响应
+ response.setUserId(userInfo.getUserId());
+ response.setUsername(userInfo.getUsername());
+ response.setNickname(userInfo.getNickname());
+ response.setAvatar(userInfo.getAvatar());
+ response.setToken(generateToken(userInfo.getUserId()));
+ response.setUserLevel(userInfo.getUserLevel());
+ response.setCoin(userInfo.getCoin());
+ response.setDiamond(userInfo.getDiamond());
+ response.setIsNewUser(isNewUser);
+
+ return response;
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ throw new RuntimeException("抖音登录失败:" + e.getMessage());
+ }
+ }
+
+ /**
+ * HTTP POST请求
+ */
+ private String httpPost(String url, String body) throws Exception
+ {
+ CloseableHttpClient httpClient = HttpClients.createDefault();
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setHeader("Content-Type", "application/json");
+ httpPost.setEntity(new StringEntity(body, "UTF-8"));
+
+ return EntityUtils.toString(httpClient.execute(httpPost).getEntity(), "UTF-8");
+ }
+
+ /**
+ * 生成令牌
+ */
+ private String generateToken(Long userId)
+ {
+ // TODO: 实现JWT或自定义token生成逻辑
+ return "token_" + userId + "_" + System.currentTimeMillis();
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/thirdparty/impl/WeChatLoginServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/thirdparty/impl/WeChatLoginServiceImpl.java
new file mode 100644
index 0000000..6b90ba0
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/thirdparty/impl/WeChatLoginServiceImpl.java
@@ -0,0 +1,156 @@
+package com.ruoyi.system.service.thirdparty.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
+import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.FateUserInfo;
+import com.ruoyi.system.domain.FateUserWechat;
+import com.ruoyi.system.domain.thirdparty.LoginRequest;
+import com.ruoyi.system.domain.thirdparty.LoginResponse;
+import com.ruoyi.system.mapper.FateUserInfoMapper;
+import com.ruoyi.system.mapper.FateUserWechatMapper;
+import com.ruoyi.system.service.thirdparty.ThirdPartyLoginService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+
+/**
+ * 微信登录服务实现
+ */
+@Service
+public class WeChatLoginServiceImpl implements ThirdPartyLoginService
+{
+ @Autowired
+ private WxMaService wxMaService;
+
+ @Autowired
+ private FateUserWechatMapper fateUserWechatMapper;
+
+ @Autowired
+ private FateUserInfoMapper fateUserInfoMapper;
+
+ @Override
+ public LoginResponse login(LoginRequest request)
+ {
+ LoginResponse response = new LoginResponse();
+
+ try
+ {
+ // 1. 通过code获取session信息
+ WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(request.getCode());
+ String openid = sessionInfo.getOpenid();
+ String sessionKey = sessionInfo.getSessionKey();
+ String unionid = sessionInfo.getUnionid();
+
+ // 2. 查询是否已绑定微信
+ FateUserWechat wechatInfo = fateUserWechatMapper.selectByOpenid(openid);
+
+ FateUserInfo userInfo;
+ boolean isNewUser = false;
+
+ if (wechatInfo == null)
+ {
+ // 3. 新用户,创建用户信息
+ userInfo = new FateUserInfo();
+ userInfo.setUsername("wx_" + openid.substring(0, 8));
+ userInfo.setNickname("微信用户");
+ userInfo.setGender(0);
+ userInfo.setUserLevel(1);
+ userInfo.setUserExp(0L);
+ userInfo.setCoin(1000L);
+ userInfo.setDiamond(100L);
+ userInfo.setStatus(1);
+ userInfo.setCreatedAt(new Date());
+ userInfo.setUpdatedAt(new Date());
+
+ // 插入用户信息
+ fateUserInfoMapper.insertFateUserInfo(userInfo);
+ isNewUser = true;
+
+ // 4. 创建微信绑定信息
+ FateUserWechat newWechatInfo = new FateUserWechat();
+ newWechatInfo.setUserId(userInfo.getUserId());
+ newWechatInfo.setOpenid(openid);
+ newWechatInfo.setUnionid(unionid);
+ newWechatInfo.setSessionKey(sessionKey);
+ newWechatInfo.setGender(0);
+ newWechatInfo.setCreatedAt(new Date());
+ newWechatInfo.setUpdatedAt(new Date());
+ fateUserWechatMapper.insertFateUserWechat(newWechatInfo);
+
+ wechatInfo = newWechatInfo;
+ }
+ else
+ {
+ // 5. 已存在用户,更新session_key
+ wechatInfo.setSessionKey(sessionKey);
+ wechatInfo.setUpdatedAt(new Date());
+ fateUserWechatMapper.updateFateUserWechat(wechatInfo);
+
+ userInfo = fateUserInfoMapper.selectFateUserInfoByUserId(wechatInfo.getUserId());
+ }
+
+ // 6. 解密用户信息(如果提供了encryptedData和iv)
+ if (StringUtils.isNotEmpty(request.getEncryptedData()) && StringUtils.isNotEmpty(request.getIv()))
+ {
+ WxMaUserInfo wxUserInfo = wxMaService.getUserService().getUserInfo(sessionKey, request.getEncryptedData(), request.getIv());
+ if (wxUserInfo != null)
+ {
+ // 更新微信信息
+ wechatInfo.setNickname(wxUserInfo.getNickName());
+ wechatInfo.setAvatar(wxUserInfo.getAvatarUrl());
+ wechatInfo.setGender(Integer.valueOf(wxUserInfo.getGender()));
+ wechatInfo.setCountry(wxUserInfo.getCountry());
+ wechatInfo.setProvince(wxUserInfo.getProvince());
+ wechatInfo.setCity(wxUserInfo.getCity());
+ wechatInfo.setUpdatedAt(new Date());
+ fateUserWechatMapper.updateFateUserWechat(wechatInfo);
+
+ // 更新用户基本信息
+ userInfo.setNickname(wxUserInfo.getNickName());
+ userInfo.setAvatar(wxUserInfo.getAvatarUrl());
+ userInfo.setGender(Integer.valueOf(wxUserInfo.getGender()));
+ if (StringUtils.isNotEmpty(wxUserInfo.getProvince()))
+ {
+ userInfo.setProvince(wxUserInfo.getProvince());
+ }
+ if (StringUtils.isNotEmpty(wxUserInfo.getCity()))
+ {
+ userInfo.setCity(wxUserInfo.getCity());
+ }
+ fateUserInfoMapper.updateFateUserInfo(userInfo);
+ }
+ }
+
+ // 7. 构建响应
+ response.setUserId(userInfo.getUserId());
+ response.setUsername(userInfo.getUsername());
+ response.setNickname(userInfo.getNickname());
+ response.setAvatar(userInfo.getAvatar());
+ response.setToken(generateToken(userInfo.getUserId()));
+ response.setUserLevel(userInfo.getUserLevel());
+ response.setCoin(userInfo.getCoin());
+ response.setDiamond(userInfo.getDiamond());
+ response.setIsNewUser(isNewUser);
+
+ return response;
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ throw new RuntimeException("微信登录失败:" + e.getMessage());
+ }
+ }
+
+ /**
+ * 生成令牌
+ */
+ private String generateToken(Long userId)
+ {
+ // TODO: 实现JWT或自定义token生成逻辑
+ // 这里简单使用userId和时间戳生成token
+ return "token_" + userId + "_" + System.currentTimeMillis();
+ }
+}