实现微信登录功能,首次使用自动保存用户信息

前端实现:
- 修改auth.vue登录逻辑
- 使用getUserProfile获取微信用户信息
- 获取用户昵称和头像URL
- 调用uni.login获取code
- 将code和用户信息发送到后端
- 登录成功后保存完整用户信息到本地

后端实现:
- 修改UserController登录接口
- 接收code参数而不是openId
- 调用微信API jscode2session获取openId
- 使用RestTemplate调用微信接口
- 支持配置微信appid和secret
- 首次登录自动创建用户并保存到数据库

配置文件:
- application.properties新增微信配置项
- wechat.appid: 微信小程序AppID
- wechat.secret: 微信小程序AppSecret

功能特点:
- 首次使用小程序自动获取微信用户信息
- 用户信息保存到user_info表
- 支持游客模式(未配置微信参数时)
- 完整的错误处理和日志记录
This commit is contained in:
H1d3rOne committed 2026-03-09 00:10:45 +08:00
1 parent 5c8fcce387
commit caac642120
3 files changed
+88 -28

No files matched your search

@@ -1,7 +1,9 @@
package xuan.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import xuan.entity.UserInfo;
import xuan.service.UserInfoService;
@@ -16,14 +18,41 @@ public class UserController {
@Autowired
private UserInfoService userInfoService;
@Value("${wechat.appid:}")
private String appId;
@Value("${wechat.secret:}")
private String appSecret;
@PostMapping("/login")
public Map<String, Object> login(@RequestBody Map<String, Object> params) {
Map<String, Object> result = new HashMap<>();
try {
String openId = params.get("openId") != null ? params.get("openId").toString() : null;
String code = params.get("code") != null ? params.get("code").toString() : null;
String nickname = params.get("nickname") != null ? params.get("nickname").toString() : "微信用户";
String avatarUrl = params.get("avatarUrl") != null ? params.get("avatarUrl").toString() : "";
String openId = null;
if (code != null && !code.isEmpty() && appId != null && !appId.isEmpty() && appSecret != null && !appSecret.isEmpty()) {
String url = String.format(
"https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
appId, appSecret, code
);
try {
RestTemplate restTemplate = new RestTemplate();
@SuppressWarnings("unchecked")
Map<String, Object> wxResult = restTemplate.getForObject(url, Map.class);
if (wxResult != null && wxResult.containsKey("openid")) {
openId = (String) wxResult.get("openid");
}
} catch (Exception e) {
System.err.println("调用微信API失败: " + e.getMessage());
}
}
if (openId == null || openId.isEmpty()) {
openId = "guest_" + System.currentTimeMillis();
}
@@ -15,3 +15,7 @@ spring.jpa.properties.hibernate.format_sql=true
# Logging
logging.level.xuan=DEBUG
# WeChat Mini Program Configuration
wechat.appid=your_wechat_appid_here
wechat.secret=your_wechat_secret_here
+54 -27
View File
@@ -85,37 +85,64 @@ export default {
this.loading = true
uni.login({
success: (loginRes) => {
if (loginRes.code) {
const openId = loginRes.code
api.userApi.loginOrRegister({
openId: openId,
nickname: this.nickname || '微信用户',
avatarUrl: this.avatarUrl
}).then(res => {
this.loading = false
if (res.success) {
uni.setStorageSync('userId', res.data.userId)
uni.setStorageSync('userInfo', JSON.stringify(res.data))
uni.getUserProfile({
desc: '用于完善用户资料',
success: (profileRes) => {
console.log('获取用户信息成功:', profileRes)
const userInfo = profileRes.userInfo
const nickname = userInfo.nickName || this.nickname || '微信用户'
const avatarUrl = userInfo.avatarUrl || this.avatarUrl
uni.login({
success: (loginRes) => {
if (loginRes.code) {
console.log('获取code成功:', loginRes.code)
uni.showToast({ title: '登录成功', icon: 'success' })
setTimeout(() => {
uni.switchTab({ url: '/pages/index/index' })
}, 1500)
api.userApi.loginOrRegister({
code: loginRes.code,
nickname: nickname,
avatarUrl: avatarUrl
}).then(res => {
this.loading = false
if (res.success) {
console.log('登录成功:', res.data)
uni.setStorageSync('userId', res.data.userId)
uni.setStorageSync('userInfo', JSON.stringify(res.data))
uni.setStorageSync('userCoins', res.data.coins || 10)
uni.setStorageSync('vipLevel', res.data.vipLevel || 0)
uni.setStorageSync('username', res.data.username || res.data.nickname)
uni.setStorageSync('userAvatar', res.data.avatarUrl)
uni.showToast({ title: '登录成功', icon: 'success' })
setTimeout(() => {
uni.switchTab({ url: '/pages/index/index' })
}, 1500)
} else {
uni.showToast({ title: res.error || '登录失败', icon: 'none' })
}
}).catch((err) => {
this.loading = false
console.error('登录失败:', err)
uni.showToast({ title: '网络错误', icon: 'none' })
})
} else {
uni.showToast({ title: res.error || '登录失败', icon: 'none' })
this.loading = false
uni.showToast({ title: '获取授权失败', icon: 'none' })
}
}).catch(() => {
},
fail: (err) => {
this.loading = false
uni.showToast({ title: '网络错误', icon: 'none' })
})
} else {
this.loading = false
uni.showToast({ title: '获取授权失败', icon: 'none' })
}
console.error('uni.login失败:', err)
uni.showToast({ title: '登录失败', icon: 'none' })
}
})
},
fail: (err) => {
this.loading = false
console.error('getUserProfile失败:', err)
uni.showToast({ title: '获取用户信息失败', icon: 'none' })
}
})
},