# Web端修改商户用户信息接口文档

## 模块概述

本模块提供商户用户信息修改功能,支持修改昵称、姓名、身份证号、账号简介、头像等个人信息。接口采用部分更新策略,只更新提供的非空字段。

---

## 接口信息

### 修改商户用户信息

#### 接口详情

- **接口名称**: 修改商户用户信息
- **接口路径**: `POST /merchantUser/updateMerchantUserInfo`
- **请求方式**: POST
- **Content-Type**: application/json
- **接口描述**: 修改指定商户用户的个人信息,支持部分字段更新
- **登录验证**: ✅ 需要（使用 `@LoginRequired` 注解）

---

## 请求参数

### 请求体（JSON格式）

| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| id | Integer | 是 | 用户ID | 12345 |
| nickName | String | 否 | 昵称 | "小明的店铺" |
| name | String | 否 | 真实姓名 | "张三" |
| idCard | String | 否 | 身份证号 | "110101199001011234" |
| accountBlurb | String | 否 | 账号简介 | "专业提供优质服务" |
| headImg | String | 否 | 头像URL | "https://example.com/avatar.jpg" |

**注意**:
- `id` 字段必须提供
- 其他字段均为可选,只需提供需要修改的字段
- 未提供的字段不会被更新

---

## 请求示例

### 示例1: 修改昵称和头像

```http
POST /merchantUser/updateMerchantUserInfo
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN

{
    "id": 12345,
    "nickName": "小明的店铺",
    "headImg": "https://example.com/avatar.jpg"
}
```

```bash
curl -X POST "http://localhost:8080/merchantUser/updateMerchantUserInfo" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "id": 12345,
    "nickName": "小明的店铺",
    "headImg": "https://example.com/avatar.jpg"
  }'
```

### 示例2: 修改真实姓名和身份证号

```json
{
    "id": 12345,
    "name": "张三",
    "idCard": "110101199001011234"
}
```

### 示例3: 只修改账号简介

```json
{
    "id": 12345,
    "accountBlurb": "专业提供优质服务，欢迎光临"
}
```

### 示例4: 修改所有信息

```json
{
    "id": 12345,
    "nickName": "小明的店铺",
    "name": "张三",
    "idCard": "110101199001011234",
    "accountBlurb": "专业提供优质服务",
    "headImg": "https://example.com/avatar.jpg"
}
```

---

## 响应参数

### 成功响应

```json
{
    "code": 200,
    "success": true,
    "data": true,
    "msg": "修改成功"
}
```

### 响应字段说明

| 字段名 | 类型 | 说明 |
|--------|------|------|
| code | Integer | 状态码, 200表示成功 |
| success | Boolean | 是否成功 |
| data | Boolean | 修改结果, true表示成功 |
| msg | String | 提示信息 |

### 失败响应

#### 1. 用户ID为空

```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "修改失败：用户ID不能为空"
}
```

#### 2. 修改失败（数据库更新失败）

```json
{
    "code": 500,
    "success": false,
    "data": false,
    "msg": "修改失败"
}
```

#### 3. 未登录或登录过期

```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "请先登录"
}
```

#### 4. 系统异常

```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "修改失败：{异常信息}"
}
```

---

## 业务逻辑说明

### 更新流程

```
┌─────────────────────────────────────────────┐
│  1. 接收请求参数                              │
│  StoreUser对象（包含id和待更新字段）         │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  2. 登录验证（AOP切面）                      │
│  - 验证Token                                 │
│  - 验证用户类型                              │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  3. 参数验证                                  │
│  检查用户ID是否为空                          │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  4. 构建更新对象                              │
│  创建新的StoreUser对象                       │
│  设置id字段                                  │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  5. 部分字段更新逻辑                          │
│  IF nickName != null AND nickName != ""      │
│    THEN 设置nickName                         │
│  IF name != null AND name != ""              │
│    THEN 设置name                             │
│  IF idCard != null AND idCard != ""          │
│    THEN 设置idCard                           │
│  IF accountBlurb != null AND accountBlurb != ""│
│    THEN 设置accountBlurb                     │
│  IF headImg != null AND headImg != ""        │
│    THEN 设置headImg                          │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  6. 执行数据库更新                            │
│  storeUserMapper.updateById(storeUser)       │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  7. 返回结果                                  │
│  updateResult > 0 → 成功                     │
│  updateResult = 0 → 失败                     │
└─────────────────────────────────────────────┘
```

### 部分更新策略

接口采用**部分更新（Partial Update）**策略:

1. **只更新提供的字段**: 
   - 只有非空（not null 且 not empty）的字段才会被更新
   - 未提供的字段保持原值不变

2. **空字符串处理**:
   - 空字符串 `""` 被视为无效值,不会更新数据库
   - 使用 `StringUtils.isNotEmpty()` 进行判断

3. **Null值处理**:
   - `null` 值的字段不会被更新
   - 如果需要清空某个字段,需要在业务层面另行处理

### 字段说明

#### 1. nickName（昵称）
- **用途**: 商户用户的显示名称
- **示例**: "小明的店铺"、"张三商户"
- **约束**: 字符串类型,可为中英文

#### 2. name（真实姓名）
- **用途**: 商户的真实姓名
- **示例**: "张三"、"李四"
- **约束**: 通常用于实名认证

#### 3. idCard（身份证号）
- **用途**: 商户的身份证号码
- **示例**: "110101199001011234"
- **约束**: 18位身份证号
- **安全**: 敏感信息,需要加密存储和传输

#### 4. accountBlurb（账号简介）
- **用途**: 商户账号的简要介绍
- **示例**: "专业提供优质服务"
- **约束**: 文本类型,可较长

#### 5. headImg（头像）
- **用途**: 商户用户的头像URL
- **示例**: "https://example.com/avatar.jpg"
- **约束**: 有效的图片URL地址

---

## 数据库操作

### 涉及的表

#### store_user（商户用户表）

**更新SQL**:
```sql
UPDATE store_user
SET 
    nick_name = ? (如果提供),
    name = ? (如果提供),
    id_card = ? (如果提供),
    account_blurb = ? (如果提供),
    head_img = ? (如果提供)
WHERE id = ?
```

**说明**:
- 使用 MyBatis-Plus 的 `updateById` 方法
- 只更新非空字段（动态SQL）
- 自动填充更新时间（如果配置了自动填充）

---

## 业务场景

### 场景1: 新用户完善个人信息

**背景**: 用户刚注册,需要完善个人信息

**请求**:
```json
{
    "id": 12345,
    "nickName": "小明的店铺",
    "name": "张三",
    "idCard": "110101199001011234",
    "accountBlurb": "专业提供优质服务"
}
```

**预期**:
- 所有提供的字段都被成功更新
- 返回 "修改成功"

---

### 场景2: 用户修改头像

**背景**: 用户只想更换头像

**请求**:
```json
{
    "id": 12345,
    "headImg": "https://example.com/new_avatar.jpg"
}
```

**预期**:
- 只有 `headImg` 字段被更新
- 其他字段保持不变
- 返回 "修改成功"

---

### 场景3: 用户修改昵称

**背景**: 用户想要更改显示昵称

**请求**:
```json
{
    "id": 12345,
    "nickName": "新店铺名称"
}
```

**预期**:
- 只有 `nickName` 字段被更新
- 返回 "修改成功"

---

### 场景4: 批量修改多个字段

**背景**: 用户一次性修改多个信息

**请求**:
```json
{
    "id": 12345,
    "nickName": "小明的优质店铺",
    "accountBlurb": "专业提供优质服务，欢迎光临",
    "headImg": "https://example.com/avatar.jpg"
}
```

**预期**:
- 三个字段都被成功更新
- 返回 "修改成功"

---

### 场景5: ID不存在

**背景**: 提供的用户ID在数据库中不存在

**请求**:
```json
{
    "id": 999999,
    "nickName": "测试店铺"
}
```

**预期**:
- 数据库更新影响行数为0
- 返回 "修改失败"

---

### 场景6: 未提供ID

**背景**: 请求中没有提供用户ID

**请求**:
```json
{
    "nickName": "测试店铺"
}
```

**预期**:
- 抛出异常："用户ID不能为空"
- 返回错误信息

---

## 安全考虑

### 1. 登录验证

接口使用 `@LoginRequired` 注解进行登录验证:

- ✅ 验证JWT Token的有效性
- ✅ 验证用户类型为 `storePlatform`
- ✅ 检查Token是否在Redis中存在（未过期/未注销）

### 2. 身份验证

**建议**:
- 前端应验证当前登录用户只能修改自己的信息
- 可以在业务层添加用户ID匹配验证
- 管理员可能需要特殊权限修改其他用户信息

### 3. 数据验证

**建议**:
- 身份证号格式验证（18位数字）
- 昵称长度限制（如1-20个字符）
- 头像URL格式验证
- 防止SQL注入（MyBatis-Plus已处理）
- 防止XSS攻击（前端需处理）

### 4. 敏感信息

**身份证号**:
- ⚠️ 高度敏感信息
- 建议加密存储
- 日志中应脱敏显示
- HTTPS传输

---

## 日志记录

### 日志级别

接口记录了详细的日志信息:

```java
// INFO级别 - 请求日志
log.info("MerchantUserController.updateMerchantUserInfo?storeUser={}", storeUser);

// INFO级别 - 业务开始
log.info("MerchantUserServiceImpl.updateMerchantUserInfo - 开始修改商户用户信息: userId={}", userId);

// DEBUG级别 - 字段更新
log.debug("MerchantUserServiceImpl.updateMerchantUserInfo - 更新昵称: {}", nickName);

// INFO级别 - 业务完成
log.info("MerchantUserServiceImpl.updateMerchantUserInfo - 修改完成: userId={}, success={}", userId, success);

// ERROR级别 - 异常日志
log.error("MerchantUserController.updateMerchantUserInfo ERROR: {}", e.getMessage(), e);
```

### 日志脱敏

**建议**对敏感字段进行脱敏:

```java
// 身份证号脱敏
String maskedIdCard = idCard.replaceAll("(\\d{6})\\d{8}(\\d{4})", "$1********$2");
log.debug("更新身份证号: {}", maskedIdCard); // 110101********1234
```

---

## 异常处理

### 异常场景

| 异常情况 | HTTP状态码 | 返回code | 返回msg |
|----------|-----------|----------|---------|
| 用户ID为空 | 200 | 500 | "修改失败：用户ID不能为空" |
| 数据库更新失败 | 200 | 500 | "修改失败" |
| 未登录 | 200 | 500 | "请先登录" |
| 系统异常 | 200 | 500 | "修改失败：{异常信息}" |

---

## 测试用例

### 测试场景1: 正常修改单个字段

**前置条件**:
- 用户已登录
- 用户ID存在

**请求**:
```json
{
    "id": 12345,
    "nickName": "新昵称"
}
```

**预期结果**:
- HTTP 200
- 返回 `{"success": true, "msg": "修改成功"}`
- 数据库中该用户的 `nick_name` 字段被更新

---

### 测试场景2: 正常修改多个字段

**请求**:
```json
{
    "id": 12345,
    "nickName": "新昵称",
    "name": "新姓名",
    "headImg": "https://example.com/new.jpg"
}
```

**预期结果**:
- 所有提供的字段都被更新
- 其他字段保持不变

---

### 测试场景3: 用户ID不存在

**请求**:
```json
{
    "id": 999999,
    "nickName": "测试"
}
```

**预期结果**:
- HTTP 200
- 返回 `{"success": false, "msg": "修改失败"}`
- 数据库无变化

---

### 测试场景4: 用户ID为空

**请求**:
```json
{
    "nickName": "测试"
}
```

**预期结果**:
- HTTP 200
- 返回 `{"success": false, "msg": "修改失败：用户ID不能为空"}`

---

### 测试场景5: 未登录

**前置条件**:
- 不携带Token或Token无效

**预期结果**:
- HTTP 200
- 返回 `{"success": false, "msg": "请先登录"}`

---

### 测试场景6: 空字符串字段

**请求**:
```json
{
    "id": 12345,
    "nickName": "",
    "name": "张三"
}
```

**预期结果**:
- `nickName` 不会被更新（空字符串被忽略）
- `name` 会被更新为 "张三"
- 返回 "修改成功"

---

## 性能优化

### 1. 数据库优化

确保 `store_user` 表的主键有索引:

```sql
-- 主键索引（通常自动创建）
ALTER TABLE store_user ADD PRIMARY KEY (id);
```

### 2. 更新策略

- ✅ 使用 MyBatis-Plus 的 `updateById` 方法（基于主键更新,性能高）
- ✅ 动态SQL,只更新非空字段
- ✅ 避免全表扫描

### 3. 缓存策略

如果用户信息频繁读取,建议:
- 使用Redis缓存用户信息
- 更新成功后清除对应的缓存
- 缓存Key: `store:user:{userId}`

```java
// 伪代码示例
@Override
public boolean updateMerchantUserInfo(StoreUser storeUser) {
    boolean success = storeUserMapper.updateById(storeUser);
    if (success) {
        // 清除缓存
        redisTemplate.delete("store:user:" + storeUser.getId());
    }
    return success;
}
```

---

## 注意事项

### 1. 部分更新

- ⚠️ 接口只更新提供的非空字段
- ⚠️ 未提供的字段保持原值
- ⚠️ 空字符串 `""` 不会更新数据库

### 2. 必填字段

- ⚠️ `id` 字段必须提供
- ⚠️ 至少提供一个需要更新的字段（否则更新无意义）

### 3. 字段长度

建议在前端和后端都进行字段长度验证:
- 昵称: 1-50个字符
- 姓名: 2-20个字符
- 身份证号: 18位
- 账号简介: 0-200个字符

### 4. 敏感信息

- ⚠️ 身份证号是敏感信息,需要特别保护
- ⚠️ 建议加密存储
- ⚠️ 日志中应脱敏
- ⚠️ 使用HTTPS传输

### 5. 并发更新

在高并发场景下,可能需要考虑:
- 使用乐观锁（version字段）
- 使用分布式锁
- 处理更新冲突

---

## 迁移说明

### 原接口（app端）

- **服务**: `alien-store`
- **路径**: `/alienStore/store/user/setUserInfo`
- **Controller**: `StoreUserController`
- **Service**: `StoreUserService.setUserInfo()`

### 新接口（web端）

- **服务**: `alien-store-platform`
- **路径**: `/merchantUser/updateMerchantUserInfo`
- **Controller**: `MerchantUserController`
- **Service**: `MerchantUserService.updateMerchantUserInfo()`

### 差异说明

| 项目 | app端 | web端 | 说明 |
|------|-------|-------|------|
| 接口路径 | `/setUserInfo` | `/updateMerchantUserInfo` | 更符合web端命名规范 |
| 登录验证 | 无明确标注 | ✅ 有（@LoginRequired） | 增加统一登录验证 |
| 参数类型 | StoreUser | StoreUser | 保持一致 |
| 返回类型 | R\<Boolean\> | R\<Boolean\> | 保持一致 |
| 业务逻辑 | ✅ | ✅ | 完全复用 |
| 日志记录 | 基础 | 详细 | 增强日志记录 |

### 复用的核心组件

1. **Mapper**: `StoreUserMapper`
2. **Entity**: `StoreUser`
3. **Util**: `StringUtils` (Apache Commons Lang3)

---

## 扩展功能建议

### 1. 字段验证

可以添加更详细的字段验证:

```java
// 身份证号验证
if (StringUtils.isNotEmpty(idCard)) {
    if (!IdCardValidator.isValid(idCard)) {
        throw new RuntimeException("身份证号格式不正确");
    }
}

// 昵称长度验证
if (StringUtils.isNotEmpty(nickName)) {
    if (nickName.length() > 50) {
        throw new RuntimeException("昵称长度不能超过50个字符");
    }
}
```

### 2. 审计日志

记录用户信息修改的审计日志:

```java
// 保存修改记录
AuditLog auditLog = new AuditLog();
auditLog.setUserId(storeUser.getId());
auditLog.setAction("UPDATE_USER_INFO");
auditLog.setDetails(JSONObject.toJSONString(storeUser));
auditLog.setOperateTime(new Date());
auditLogMapper.insert(auditLog);
```

### 3. 变更通知

当关键信息修改时,发送通知:

```java
// 如果修改了身份证号,发送通知
if (StringUtils.isNotEmpty(storeUser.getIdCard())) {
    notificationService.send(userId, "您的身份证号已更新");
}
```

---

## 更新日志

### 2025-11-17

**新增接口**:
- ✅ `POST /merchantUser/updateMerchantUserInfo` - 修改商户用户信息

**核心功能**:
- ✅ 支持部分字段更新
- ✅ 动态SQL,只更新非空字段
- ✅ 登录验证（@LoginRequired）
- ✅ 详细日志记录
- ✅ 完善异常处理
- ✅ 参数验证（用户ID）

**涉及文件**:
- `MerchantUserController.java` - 更新
- `MerchantUserService.java` - 更新
- `MerchantUserServiceImpl.java` - 更新

**代码质量**:
- ✅ Linter检查：无错误
- ✅ 日志记录：详细
- ✅ 异常处理：完善
- ✅ 代码注释：完整

**开发人员**: ssk

---

**文档版本**: v1.0  
**最后更新**: 2025-11-17  
**维护人员**: ssk






