@ApiOperation(value = "保存用户信息") @PutMapping("/saveUserInfo") public JsonData<String> saveUserInfo(HttpServletRequest request, @RequestParam("encryptedData") String encryptedData, @RequestParam("iv") String iv) 前端如何传值
时间: 2024-01-11 08:06:09 浏览: 94
前端需要使用POST或PUT请求将加密数据encryptedData和偏移量iv作为请求参数传递给该接口。具体可以使用AJAX或Fetch等技术实现,如下所示:
```javascript
const encryptedData = "加密后的数据";
const iv = "偏移量";
fetch('/saveUserInfo', {
method: 'PUT',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `encryptedData=${encryptedData}&iv=${iv}`
}).then(response => {
// 处理响应结果
}).catch(error => {
// 处理异常情况
});
```
其中,需要注意设置请求头Content-Type为application/x-www-form-urlencoded,将请求参数以URL编码的形式放在请求体中。
相关问题
<template> <view class="user-center"> <!-- 头部导航 --> <view class="header"> <text class="title">个人中心</text> </view> <!-- 个人信息卡片 --> <view class="info-card"> <!-- 头像区域 --> <view class="info-item" @click="editAvatar"> <text class="label">头像</text> <view class="content"> <image :src="userInfo.avatar" class="avatar" mode="aspectFill" /> <text class="arrow">></text> </view> </view> <!-- 昵称区域 --> <view class="info-item" @click="editNickname"> <text class="label">昵称</text> <view class="content"> <text>{{ userInfo.nickname || '未设置' }}</text> <text class="arrow">></text> </view> </view> <!-- 性别区域 --> <view class="info-item" @click="editGender"> <text class="label">性别</text> <view class="content"> <text>{{ userInfo.gender || '未设置' }}</text> <text class="arrow">></text> </view> </view> <!-- 年龄区域 --> <view class="info-item" @click="editAge"> <text class="label">年龄</text> <view class="content"> <text>{{ userInfo.age || '未设置' }}</text> <text class="arrow">></text> </view> </view> </view> <!-- 退出登录按钮 --> <view class="logout-btn" @click="logout"> 退出登录 </view> <!-- 编辑弹窗 --> <view class="modal" v-if="showModal"> <view class="modal-content"> <view class="modal-header"> <text class="modal-title">{{ modalTitle }}</text> <text class="close" @click="closeModal">×</text> </view> <view class="modal-body"> <view v-if="modalType === 'avatar'"> <text>选择头像来源</text> <view class="avatar-options"> <view class="option" @click="chooseAvatar('camera')"> <image src="/static/camera.jpg" class="option-icon" mode="aspectFill" /> <text>拍照</text> </view> <view class="option" @click="chooseAvatar('album')"> <image src="/static/album.jpg" class="option-icon" mode="aspectFill" /> <text>从相册选择</text> </view> </view> </view> <view v-else-if="modalType === 'nickname'"> <input type="text" class="input" placeholder="请输入昵称" v-model="editValue" /> </view> <view v-else-if="modalType === 'gender'"> <view class="gender-options"> <view class="option" :class="{ active: editValue === '男' }" @click="editValue = '男'"> 男 </view> <view class="option" :class="{ active: editValue === '女' }" @click="editValue = '女'"> 女 </view> <view class="option" :class="{ active: editValue === '保密' }" @click="editValue = '保密'"> 保密 </view> </view> </view> <view v-else-if="modalType === 'age'"> <input type="number" class="input" placeholder="请输入年龄" v-model="editValue" /> </view> </view> <view class="modal-footer"> <button class="cancel-btn" @click="closeModal">取消</button> <button class="confirm-btn" @click="confirmEdit">确定</button> </view> </view> </view> </view> </template> <script> export default { data() { return { userInfo: { avatar: '/static/user.jpg', nickname: uni.getStorageSync('currentUser') || '', gender: '', age: '' }, showModal: false, modalType: '', modalTitle: '', editValue: '' } }, onShow() { this.loadUserInfo(); }, methods: { loadUserInfo() { const savedInfo = uni.getStorageSync('userInfo') || {}; this.userInfo = { ...this.userInfo, ...savedInfo }; console.log('加载的用户信息:', this.userInfo); }, editAvatar() { this.modalType = 'avatar'; this.modalTitle = '更换头像'; this.showModal = true; }, // 优化后的chooseAvatar方法(核心修改) async chooseAvatar(type) { // 显示加载提示,提升用户体验 uni.showLoading({ title: '处理中...' }); try { // 1. 选择图片(Promise封装,便于async/await) const chooseRes = await new Promise((resolve, reject) => { uni.chooseImage({ count: 1, sourceType: type === 'camera' ? ['camera'] : ['album'], sizeType: ['compressed'], // 优先压缩,减少文件大小 success: resolve, fail: (err) => reject(new Error(`选择图片失败: ${err.errMsg}`)) }); }); // 2. 准备文件系统和路径 const fs = wx.getFileSystemManager(); const timestamp = Date.now(); // 时间戳确保文件名唯一 const fileName = `avatar_${timestamp}.png`; const savedPath = `${wx.env.USER_DATA_PATH}/${fileName}`; // 固定存储目录 // 3. 保存文件为永久路径 await new Promise((resolve, reject) => { fs.saveFile({ tempFilePath: chooseRes.tempFilePaths[0], filePath: savedPath, success: (res) => { console.log('[user-center] 图片保存成功,路径:', res.savedFilePath); resolve(res.savedFilePath); }, fail: (err) => reject(new Error(`保存图片失败: ${err.errMsg}`)) }); }); // 4. 更新用户信息并同步存储 const userInfo = { ...uni.getStorageSync('userInfo') || {} }; // 深拷贝避免引用问题 userInfo.avatar = savedPath; uni.setStorageSync('userInfo', userInfo); console.log('[user-center] 本地存储已更新:', userInfo.avatar); // 5. 实时更新视图并通知其他组件 this.userInfo.avatar = savedPath; // 更新当前组件 uni.$emit('avatarUpdated', savedPath); // 通知my-title组件 console.log('[user-center] 已发送全局更新事件'); // 6. 关闭弹窗和加载提示 this.closeModal(); uni.hideLoading(); uni.showToast({ title: '头像更新成功', icon: 'success' }); } catch (err) { // 统一错误处理 console.error('[user-center] 头像处理异常:', err); uni.hideLoading(); // 确保加载提示关闭 uni.showToast({ title: err.message || '更新失败,请重试', icon: 'none', duration: 2000 }); } }, editNickname() { this.modalType = 'nickname'; this.modalTitle = '修改昵称'; this.editValue = this.userInfo.nickname || ''; this.showModal = true; }, editGender() { this.modalType = 'gender'; this.modalTitle = '修改性别'; this.editValue = this.userInfo.gender || '保密'; this.showModal = true; }, editAge() { this.modalType = 'age'; this.modalTitle = '修改年龄'; this.editValue = this.userInfo.age || ''; this.showModal = true; }, confirmEdit() { if (this.modalType === 'nickname') { this.userInfo.nickname = this.editValue; } else if (this.modalType === 'gender') { this.userInfo.gender = this.editValue; } else if (this.modalType === 'age') { this.userInfo.age = this.editValue; } this.saveUserInfo(); this.closeModal(); uni.showToast({ title: '修改成功', icon: 'success' }); }, closeModal() { this.showModal = false; this.editValue = ''; }, saveUserInfo() { uni.setStorageSync('userInfo', this.userInfo); }, logout() { uni.showModal({ title: '确认退出', content: '确定要退出当前账号吗?', success: (res) => { if (res.confirm) { uni.removeStorageSync('isLoggedIn'); uni.removeStorageSync('currentUser'); uni.removeStorageSync('userInfo'); uni.navigateTo({ url: '/pages/login/login' }); } } }); } } } </script> <style lang="scss"> .user-center { background-color: #f5f5f5; min-height: 100vh; .header { height: 100rpx; background-color: #fff; display: flex; align-items: center; justify-content: center; border-bottom: 1rpx solid #eee; .title { font-size: 36rpx; font-weight: bold; color: #333; } } .info-card { background-color: #fff; margin-top: 20rpx; padding: 0 30rpx; .info-item { display: flex; align-items: center; height: 100rpx; border-bottom: 1rpx solid #eee; .label { font-size: 32rpx; color: #333; width: 140rpx; } .content { flex: 1; display: flex; align-items: center; justify-content: flex-end; gap: 20rpx; .avatar { width: 80rpx; height: 80rpx; border-radius: 50%; } text { font-size: 30rpx; color: #666; } } .arrow { font-size: 30rpx; color: #999; margin-left: 10rpx; } } } .logout-btn { margin: 60rpx 30rpx; height: 90rpx; background-color: #ff4d4f; color: #fff; font-size: 34rpx; border-radius: 10rpx; display: flex; align-items: center; justify-content: center; } .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; .modal-content { background-color: #fff; width: 600rpx; border-radius: 20rpx; .modal-header { height: 100rpx; display: flex; align-items: center; justify-content: space-between; padding: 0 30rpx; border-bottom: 1rpx solid #eee; .modal-title { font-size: 34rpx; font-weight: bold; color: #333; } .close { font-size: 40rpx; color: #999; } } .modal-body { padding: 30rpx; .avatar-options { display: flex; justify-content: space-around; margin-top: 40rpx; .option { display: flex; flex-direction: column; align-items: center; .option-icon { width: 120rpx; height: 120rpx; border-radius: 10rpx; margin-bottom: 20rpx; } } } .gender-options { display: flex; justify-content: space-around; margin-top: 20rpx; .option { width: 160rpx; height: 80rpx; border: 1rpx solid #ddd; border-radius: 10rpx; display: flex; align-items: center; justify-content: center; font-size: 32rpx; &.active { border-color: #007aff; color: #007aff; } } } .input { width: 100%; height: 80rpx; border: 1rpx solid #ddd; border-radius: 10rpx; padding: 0 20rpx; font-size: 32rpx; } } .modal-footer { display: flex; height: 100rpx; border-top: 1rpx solid #eee; .cancel-btn, .confirm-btn { flex: 1; display: flex; align-items: center; justify-content: center; font-size: 34rpx; } .confirm-btn { color: #007aff; font-weight: bold; } } } } } </style> <template> <view class="my-title"> <!-- Logo --> <navigator class="logo" url="/pages/index/index"> <image class="logo-img" src="/static/logo.png" mode="aspectFill" /> </navigator> <!-- 搜索框 --> <view class="search-icon"> <image src="/static/search.jpg" mode="aspectFill" /> </view> <!-- 用户头像 --> <view class="user-icon"> <image :src="userAvatar" mode="aspectFill" @error="handleImgError" @load="handleImgLoad" :key="avatarKey" /> </view> <!-- 下载APP按钮 --> <view class="down-app"> 下载APP </view> </view> </template> <script> export default { name: "MyTitle", data() { return { userAvatar: '/static/user.jpg', avatarKey: 0, syncTimer: null }; }, // 页面显示时强制刷新(解决切页后不同步问题) onShow() { console.log('[my-title] 页面显示,强制加载最新头像'); this.loadAvatar(); }, onLoad() { console.log('[my-title] 初始化,开始监听事件'); // 监听全局事件(确保任何页面修改都能触发) uni.$on('avatarUpdated', (newPath) => { console.log('[my-title] 全局事件触发,新路径:', newPath); this.updateAvatar(newPath); }); // 定时同步(3秒一次,平衡性能和可靠性) this.syncTimer = setInterval(() => { console.log('[my-title] 定时同步触发'); this.loadAvatar(); }, 3000); }, onUnload() { console.log('[my-title] 页面卸载,清理资源'); uni.$off('avatarUpdated'); clearInterval(this.syncTimer); }, methods: { // 更新头像(极简逻辑,减少拦截) updateAvatar(newPath) { if (!newPath) { console.error('[my-title] 路径为空,跳过更新'); return; } // 直接更新,不做过度验证(信任用户中心传递的路径) this.userAvatar = newPath; this.avatarKey++; // 强制重新渲染 console.log('[my-title] 头像已更新为:', newPath); }, // 从存储加载(核心:确保读取最新数据) loadAvatar() { try { const userInfo = uni.getStorageSync('userInfo') || {}; const storedPath = userInfo.avatar || '/static/user.jpg'; console.log('[my-title] 从存储读取到路径:', storedPath); // 只要路径不同就更新 if (storedPath !== this.userAvatar) { this.updateAvatar(storedPath); } } catch (e) { console.error('[my-title] 读取存储失败:', e); } }, // 加载成功回调 handleImgLoad() { console.log('[my-title] 头像加载成功,当前路径:', this.userAvatar); }, // 加载失败处理(增强重试) handleImgError(e) { console.error('[my-title] 头像加载失败,错误:', e.errMsg); console.error('[my-title] 失败路径:', this.userAvatar); // 重试存储中的最新路径 setTimeout(() => { console.log('[my-title] 失败后重试加载...'); this.loadAvatar(); }, 1000); } } } </script> <style scoped> /* 样式保持不变 */ .my-title{ display: flex; align-items: center; padding: 10rpx 20rpx; background-color: #fff; height: 100rpx; } .logo { flex: 2; display: flex; align-items: center; } .logo-img { width: 180rpx; height: 60rpx; } .search-icon { flex: 3; display: flex; justify-content: center; align-items: center; } .search-icon image { width: 60rpx; height: 44rpx; } .user-icon { flex: 1; display: flex; justify-content: center; align-items: center; } .user-icon image { width: 60rpx; height: 60rpx; border-radius: 50%; border: 1rpx solid #eee; } .down-app { flex: 2; font-size: 30rpx; background-color: #87CEEB; color: #fff; border-radius: 10rpx; padding: 10rpx 20rpx; margin-left: 10rpx; display: flex; align-items: center; justify-content: center; } </style>如何修改头像后用户头像也会发生改变
<think>我们正在讨论用户头像修改后的实时同步更新问题。根据引用内容,特别是引用[1]和引用[3],我们可以总结出以下关键点:
1. 引用[1]指出,当用户重新上传头像后,需要将新头像的路径更新到Cookie中,否则页面显示的仍是旧头像(因为页面从Cookie读取头像路径)。
2. 引用[3]则提供了一个在UCHOME(一种社交网络系统)中更新头像时间的示例,通过一个特定的URL(avatorupdate.php)来触发更新,并检查一个密钥(authkey)和用户ID(uid)。
然而,用户的需求是“在修改头像后实现用户头像的实时同步更新”。这通常涉及多个方面:前端(浏览器)的更新、后端(服务器)的存储更新、以及可能涉及的其他系统(如CDN缓存刷新、多个应用间的同步等)。
### 解决方案思路
1. **更新Cookie(针对浏览器缓存)**:当用户上传新头像后,服务器在响应中设置新的头像路径到Cookie,这样下次页面加载时就会使用新头像。但注意,如果页面已经打开,可能需要刷新页面才能重新读取Cookie。或者,我们可以通过JavaScript动态更新页面上的头像图片,而不必刷新整个页面。
2. **更新服务器存储**:新头像需要保存到服务器,并覆盖旧头像(或生成新的头像文件路径)。同时,更新用户记录中的头像路径。
3. **避免浏览器缓存问题**:为了确保浏览器加载新头像而不是缓存中的旧头像,可以在头像URL后添加一个时间戳参数(例如:`avatar.jpg?t=1620000000`),这样每次更新头像后,URL就会改变,强制浏览器重新加载。
4. **多系统同步(如果涉及)**:如引用[3]所示,在UCHOME系统中,他们通过一个特定的接口来更新头像的更新时间,以便其他模块(如论坛)能够同步更新。如果我们的系统也由多个子系统组成,那么需要设计一个同步机制。例如:
- 提供一个接口,当头像更新时,调用该接口通知其他子系统。
- 或者,其他子系统在显示头像时,通过主系统获取最新的头像URL(并检查更新时间)。
5. **实时更新页面上的头像(无刷新)**:在用户上传头像后,通过Ajax将新头像上传到服务器,服务器返回新的头像URL(或新的URL加上时间戳),然后通过JavaScript更新页面中所有头像元素的`src`属性。
### 具体实现步骤
#### 前端(HTML + JavaScript)
假设页面上有一个头像图片元素:
```html
<img id="user-avatar" src="/avatars/old_avatar.jpg" alt="Avatar">
```
当用户选择新头像并上传后,使用JavaScript处理:
```javascript
// 假设使用FormData上传头像文件
function uploadAvatar(file) {
let formData = new FormData();
formData.append('avatar', file);
fetch('/update_avatar', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 更新页面上的头像
let avatarElement = document.getElementById('user-avatar');
// 使用新的URL,并加上时间戳避免缓存
avatarElement.src = data.newAvatarUrl + '?t=' + new Date().getTime();
// 如果需要,更新Cookie(这里假设服务器已经在响应中设置了新的Cookie,但如果我们想立即更新页面其他部分,可以手动设置)
// 注意:直接设置Cookie可能不安全,最好由服务器在响应头中设置HttpOnly的Cookie,这里只是更新显示
// 另外,如果页面其他地方通过读取Cookie来显示头像,那么需要刷新页面(或者同样用JS更新)
}
});
}
```
#### 后端(服务器)
以伪代码为例(例如使用Node.js Express):
```javascript
app.post('/update_avatar', (req, res) => {
// 处理上传的头像文件,保存到服务器,并生成新的文件路径
// ... (保存文件,生成新的文件名或路径)
// 更新用户数据库记录中的头像路径
let newAvatarUrl = '/avatars/new_avatar.jpg'; // 新路径
// 更新数据库...
// 设置新的头像路径到Cookie(可选,如果前端依赖Cookie)
res.cookie('avatar', newAvatarUrl, { maxAge: 30*24*60*60*1000 }); // 设置30天有效期
// 返回成功响应,包含新的头像URL
res.json({ success: true, newAvatarUrl: newAvatarUrl });
});
```
#### 多系统同步
如果系统由多个独立的应用组成(例如主站和论坛),那么可以:
1. 在主站的头像更新接口中,除了更新自身数据库和Cookie,还要调用其他子系统的更新接口(如引用[3]中的`avatorupdate.php`)。
2. 或者,其他子系统在显示头像时,通过主系统的API获取最新头像URL(并缓存一段时间,但缓存时间不宜过长)。
### 注意事项
- **安全性**:在引用[3]中,他们使用了一个固定的密钥(authkey)来验证调用。在多系统同步时,确保接口调用的安全性(例如使用密钥验证或OAuth)。
- **性能**:如果用户量很大,频繁更新头像可能带来性能压力,考虑使用CDN和缓存策略,但要注意缓存失效(通过URL版本控制或时间戳)。
- **存储**:头像文件存储建议使用独立的存储服务(如云存储),并考虑不同尺寸的头像生成(缩略图等)。
### 总结
实现用户头像实时同步更新的关键步骤包括:
1. 前端通过Ajax上传新头像并动态更新页面上的头像图片(避免整页刷新)。
2. 后端更新用户记录的头像路径,并设置新的Cookie(如果使用)。
3. 通过URL添加时间戳或版本号来避免浏览器缓存问题。
4. 对于多系统环境,设计同步机制(接口调用或API获取)。
这样,用户上传新头像后,页面上的头像就能立即更新,其他子系统也能在短时间内同步更新。
§§相关问题§§
1. 如何避免用户头像被浏览器缓存导致更新延迟?
2. 在多系统架构中,如何安全高效地同步用户头像?
3. 用户头像上传后如何生成不同尺寸的缩略图?</think>### 用户头像实时同步更新解决方案
要实现用户头像修改后的实时同步更新,需解决**浏览器缓存**和**多系统同步**两大核心问题。以下是完整实现方案:
#### 一、浏览器缓存更新(前端核心)
1. **修改Cookie存储**
上传新头像时,立即更新Cookie中的头像路径(添加时间戳参数强制刷新):
```javascript
// 上传成功后执行(示例:jQuery)
function updateAvatar(newPath) {
// 添加时间戳参数避免缓存
const timestamp = new Date().getTime();
const newAvatarUrl = `${newPath}?t=${timestamp}`;
// 更新Cookie(有效期为30天)
document.cookie = `user_avatar=${encodeURIComponent(newAvatarUrl)}; max-age=${30*24*60*60}; path=/`;
// 实时更新页面显示
$('.user-avatar').attr('src', newAvatarUrl);
}
```
2. **强制刷新图像缓存**
在头像URL后添加时间戳参数(如 `avatar.jpg?t=1620000000`),使浏览器视为新资源。
#### 二、服务器端同步(后端核心)
1. **更新用户记录时间戳**
在用户表中添加 `avatar_updated_at` 字段:
```sql
ALTER TABLE users ADD COLUMN avatar_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
```
2. **上传头像时更新时间戳**
保存头像文件后立即更新数据库:
```python
# Python Flask 示例
@app.route('/upload_avatar', methods=['POST'])
def upload_avatar():
file = request.files['avatar']
user_id = session['user_id']
# 保存文件(示例伪代码)
new_path = save_avatar(file, user_id)
# 更新数据库时间戳
db.execute("""
UPDATE users
SET avatar_path = ?, avatar_updated_at = CURRENT_TIMESTAMP
WHERE id = ?
""", (new_path, user_id))
return jsonify(success=True, new_path=new_path)
```
#### 三、多系统同步方案(引用[3]原理扩展)
```php
<?php
// avatar_sync.php
if ($_GET['authkey'] == 'YOUR_SECRET_KEY' && is_numeric($_GET['uid'])) {
$user_id = intval($_GET['uid']);
// 获取最新头像路径
$avatar_data = fetch_from_database("SELECT avatar_path, avatar_updated_at FROM users WHERE id = $user_id");
// 更新所有子系统(示例:论坛、博客等)
update_sub_system('forum', $user_id, $avatar_data);
update_sub_system('blog', $user_id, $avatar_data);
// 返回同步结果
echo json_encode(['status' => 'synced', 'time' => time()]);
}
function update_sub_system($system, $user_id, $avatar_data) {
// 调用各子系统API更新头像
// 示例:通过CURL发送更新请求
}
?>
```
#### 四、实时同步流程
1. **用户上传头像** → 2. **服务器更新路径和时间戳** → 3. **前端更新Cookie和DOM** → 4. **触发同步脚本更新子系统** → 5. **各系统通过时间戳检查更新**
#### 五、缓存控制增强
在HTTP响应头中添加缓存策略:
```nginx
# Nginx 配置
location ~* \.(jpg|jpeg|png)$ {
add_header Cache-Control "no-cache, max-age=0";
expires epoch;
}
```
> **关键点**:时间戳机制确保所有系统通过比较 `avatar_updated_at` 字段值判断是否需要更新头像[^3],结合URL参数强制刷新浏览器缓存[^1],实现全链路实时更新。
@PutMapping("/saveUserInfo")和@Postapping("/saveUserInfo")前端传值是否一致
是的,无论是@PutMapping还是@PostMapping,前端传值的方式都是一致的,都是将参数通过请求体传递给后端。具体实现方式可以使用AJAX或Fetch等技术,如下所示:
```javascript
const encryptedData = "加密后的数据";
const iv = "偏移量";
fetch('/saveUserInfo', {
method: 'PUT', // 或 'POST'
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
encryptedData: encryptedData,
iv: iv
})
}).then(response => {
// 处理响应结果
}).catch(error => {
// 处理异常情况
});
```
其中,需要注意设置请求头Content-Type为application/json,将请求参数以JSON字符串的形式放在请求体中。
阅读全文
相关推荐















