feat: 公众端个人资料更新接口 DTO 校验与前端对齐
Made-with: Cursor
This commit is contained in:
parent
9913b3453b
commit
92f7bf5419
@ -6,11 +6,13 @@ import com.lesingle.common.util.SecurityUtil;
|
||||
import com.lesingle.modules.biz.contest.entity.BizContestRegistration;
|
||||
import com.lesingle.modules.pub.service.PublicActivityService;
|
||||
import com.lesingle.modules.pub.service.PublicInteractionService;
|
||||
import com.lesingle.modules.pub.dto.PublicProfileUpdateDto;
|
||||
import com.lesingle.modules.pub.service.PublicProfileService;
|
||||
import com.lesingle.modules.pub.service.PublicUserWorkService;
|
||||
import com.lesingle.modules.ugc.entity.UgcWork;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@ -34,11 +36,10 @@ public class PublicProfileController {
|
||||
}
|
||||
|
||||
@PutMapping("/profile")
|
||||
@Operation(summary = "更新个人资料")
|
||||
public Result<Void> updateProfile(@RequestBody Map<String, String> body) {
|
||||
@Operation(summary = "更新个人资料", description = "请求体 JSON:nickname(必填)、city、gender(male/female)、avatar(可选,不传则不改头像)")
|
||||
public Result<Void> updateProfile(@Valid @RequestBody PublicProfileUpdateDto dto) {
|
||||
Long userId = SecurityUtil.getCurrentUserId();
|
||||
publicProfileService.updateProfile(userId,
|
||||
body.get("nickname"), body.get("city"), body.get("avatar"), body.get("gender"));
|
||||
publicProfileService.updateProfile(userId, dto);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
package com.lesingle.modules.pub.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 公众端「我的」个人资料更新,与前端 PUT /public/mine/profile 请求体一致(camelCase)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "公众端更新个人资料")
|
||||
public class PublicProfileUpdateDto {
|
||||
|
||||
@NotBlank(message = "昵称不能为空")
|
||||
@Size(min = 1, max = 20, message = "昵称长度为1-20个字符")
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
|
||||
@Size(max = 50, message = "城市长度不超过50个字符")
|
||||
@Schema(description = "城市,可空表示清空")
|
||||
private String city;
|
||||
|
||||
@Schema(description = "性别:male / female,可空表示清空")
|
||||
private String gender;
|
||||
|
||||
@Size(max = 512, message = "头像地址过长")
|
||||
@Schema(description = "头像 URL,不传则不修改")
|
||||
private String avatar;
|
||||
}
|
||||
@ -4,10 +4,12 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.lesingle.common.constants.TenantConstants;
|
||||
import com.lesingle.common.enums.CommonStatus;
|
||||
import com.lesingle.common.enums.ErrorCode;
|
||||
import com.lesingle.common.enums.UserSource;
|
||||
import com.lesingle.common.enums.UserType;
|
||||
import com.lesingle.common.exception.BusinessException;
|
||||
import com.lesingle.modules.pub.dto.CreateChildDto;
|
||||
import com.lesingle.modules.pub.dto.PublicProfileUpdateDto;
|
||||
import com.lesingle.modules.sys.entity.SysUser;
|
||||
import com.lesingle.modules.sys.entity.SysTenant;
|
||||
import com.lesingle.modules.sys.mapper.SysUserMapper;
|
||||
@ -21,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@ -62,15 +65,22 @@ public class PublicProfileService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新个人资料
|
||||
* 更新个人资料(与 {@link PublicProfileUpdateDto} 字段一致)
|
||||
*/
|
||||
public void updateProfile(Long userId, String nickname, String city, String avatar, String gender) {
|
||||
public void updateProfile(Long userId, PublicProfileUpdateDto dto) {
|
||||
String g = dto.getGender();
|
||||
if (StringUtils.hasText(g) && !"male".equals(g) && !"female".equals(g)) {
|
||||
throw BusinessException.of(ErrorCode.BAD_REQUEST, "性别须为 male 或 female");
|
||||
}
|
||||
|
||||
LambdaUpdateWrapper<SysUser> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(SysUser::getId, userId);
|
||||
if (nickname != null) wrapper.set(SysUser::getNickname, nickname);
|
||||
if (city != null) wrapper.set(SysUser::getCity, city);
|
||||
if (avatar != null) wrapper.set(SysUser::getAvatar, avatar);
|
||||
if (gender != null) wrapper.set(SysUser::getGender, gender);
|
||||
wrapper.set(SysUser::getNickname, dto.getNickname().trim());
|
||||
wrapper.set(SysUser::getCity, StringUtils.hasText(dto.getCity()) ? dto.getCity().trim() : null);
|
||||
wrapper.set(SysUser::getGender, StringUtils.hasText(g) ? g.trim() : null);
|
||||
if (dto.getAvatar() != null) {
|
||||
wrapper.set(SysUser::getAvatar, StringUtils.hasText(dto.getAvatar()) ? dto.getAvatar().trim() : null);
|
||||
}
|
||||
sysUserMapper.update(null, wrapper);
|
||||
}
|
||||
|
||||
|
||||
@ -107,6 +107,8 @@ export interface PublicUser {
|
||||
phone: string | null;
|
||||
city: string | null;
|
||||
avatar: string | null;
|
||||
/** 与后端 SysUser.gender 一致:male / female */
|
||||
gender?: string | null;
|
||||
tenantId: number;
|
||||
tenantCode: string;
|
||||
userSource: string;
|
||||
@ -118,6 +120,14 @@ export interface PublicUser {
|
||||
childrenCount?: number;
|
||||
}
|
||||
|
||||
/** PUT /public/mine/profile 请求体,与后端 PublicProfileUpdateDto 对齐 */
|
||||
export interface PublicProfileUpdatePayload {
|
||||
nickname: string;
|
||||
city?: string;
|
||||
gender?: string;
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
token: string;
|
||||
user: PublicUser;
|
||||
@ -144,12 +154,8 @@ export const publicAuthApi = {
|
||||
export const publicProfileApi = {
|
||||
getProfile: (): Promise<PublicUser> => publicApi.get("/public/mine/profile"),
|
||||
|
||||
updateProfile: (data: {
|
||||
nickname?: string;
|
||||
city?: string;
|
||||
avatar?: string;
|
||||
gender?: string;
|
||||
}) => publicApi.put("/public/mine/profile", data),
|
||||
updateProfile: (data: PublicProfileUpdatePayload) =>
|
||||
publicApi.put("/public/mine/profile", data),
|
||||
};
|
||||
|
||||
// ==================== 子女管理 ====================
|
||||
|
||||
@ -70,17 +70,27 @@
|
||||
|
||||
<!-- 编辑个人信息弹窗 -->
|
||||
<a-modal v-model:open="showEditModal" title="编辑个人信息" :footer="null" :width="400" centered>
|
||||
<a-form layout="vertical" @finish="handleSaveProfile" style="margin-top: 16px;">
|
||||
<a-form-item label="昵称" :rules="[{ required: true, message: '请输入昵称' }]">
|
||||
<a-form
|
||||
:model="editForm"
|
||||
layout="vertical"
|
||||
autocomplete="off"
|
||||
@finish="handleSaveProfile"
|
||||
style="margin-top: 16px;"
|
||||
>
|
||||
<a-form-item
|
||||
label="昵称"
|
||||
name="nickname"
|
||||
:rules="[{ required: true, message: '请输入昵称' }, { max: 20, message: '最多20个字符' }]"
|
||||
>
|
||||
<a-input v-model:value="editForm.nickname" placeholder="你的昵称" :maxlength="20" />
|
||||
</a-form-item>
|
||||
<a-form-item label="性别">
|
||||
<a-form-item label="性别" name="gender">
|
||||
<a-select v-model:value="editForm.gender" placeholder="选择性别" allow-clear>
|
||||
<a-select-option value="male">男</a-select-option>
|
||||
<a-select-option value="female">女</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="城市">
|
||||
<a-form-item label="城市" name="city" :rules="[{ max: 50, message: '最多50个字符' }]">
|
||||
<a-input v-model:value="editForm.city" placeholder="你所在的城市" :maxlength="50" />
|
||||
</a-form-item>
|
||||
<a-button type="primary" html-type="submit" block :loading="editLoading"
|
||||
@ -104,7 +114,7 @@ import {
|
||||
LogoutOutlined,
|
||||
EditOutlined,
|
||||
} from "@ant-design/icons-vue"
|
||||
import { publicProfileApi, publicMineApi } from "@/api/public"
|
||||
import { publicProfileApi, publicMineApi, type PublicProfileUpdatePayload } from "@/api/public"
|
||||
import { useAicreateStore } from "@/stores/aicreate"
|
||||
|
||||
const router = useRouter()
|
||||
@ -149,12 +159,39 @@ const childrenDesc = computed(() => {
|
||||
return count > 0 ? `已创建 ${count} 个子女账号` : "为子女创建独立账号"
|
||||
})
|
||||
|
||||
/** 与登录态 localStorage `public_user` 合并,避免改资料后其它页仍显示旧昵称 */
|
||||
const syncPublicUserCache = (patch: { nickname?: string; city?: string | null; gender?: string | null; avatar?: string | null }) => {
|
||||
try {
|
||||
const raw = localStorage.getItem("public_user")
|
||||
if (!raw) return
|
||||
const prev = JSON.parse(raw) as Record<string, unknown>
|
||||
localStorage.setItem(
|
||||
"public_user",
|
||||
JSON.stringify({
|
||||
...prev,
|
||||
...(patch.nickname !== undefined && { nickname: patch.nickname }),
|
||||
...(patch.city !== undefined && { city: patch.city }),
|
||||
...(patch.gender !== undefined && { gender: patch.gender }),
|
||||
...(patch.avatar !== undefined && { avatar: patch.avatar }),
|
||||
}),
|
||||
)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
const fetchProfile = async () => {
|
||||
try {
|
||||
user.value = await publicProfileApi.getProfile()
|
||||
editForm.nickname = user.value?.nickname || ""
|
||||
editForm.city = user.value?.city || ""
|
||||
editForm.gender = user.value?.gender || ""
|
||||
syncPublicUserCache({
|
||||
nickname: user.value?.nickname,
|
||||
city: user.value?.city ?? null,
|
||||
gender: user.value?.gender ?? null,
|
||||
avatar: user.value?.avatar ?? null,
|
||||
})
|
||||
} catch {
|
||||
message.error("获取个人信息失败,请重新登录")
|
||||
handleLogout()
|
||||
@ -172,17 +209,21 @@ const fetchCounts = async () => {
|
||||
|
||||
const handleSaveProfile = async () => {
|
||||
editLoading.value = true
|
||||
const nickname = editForm.nickname.trim()
|
||||
const payload: PublicProfileUpdatePayload = {
|
||||
nickname,
|
||||
...(editForm.city?.trim() ? { city: editForm.city.trim() } : { city: "" }),
|
||||
...(editForm.gender ? { gender: editForm.gender } : { gender: "" }),
|
||||
}
|
||||
try {
|
||||
await publicProfileApi.updateProfile({
|
||||
nickname: editForm.nickname,
|
||||
city: editForm.city || undefined,
|
||||
gender: editForm.gender || undefined,
|
||||
})
|
||||
await publicProfileApi.updateProfile(payload)
|
||||
message.success("保存成功")
|
||||
showEditModal.value = false
|
||||
fetchProfile()
|
||||
} catch {
|
||||
message.error("保存失败")
|
||||
await fetchProfile()
|
||||
} catch (e: unknown) {
|
||||
const msg =
|
||||
(e as { response?: { data?: { message?: string } } })?.response?.data?.message || "保存失败"
|
||||
message.error(msg)
|
||||
} finally {
|
||||
editLoading.value = false
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user