mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 10:57:13 +08:00 
			
		
		
		
	新增:新增修改基础信息 API(优化 Jackson 针对通用枚举接口 IEnum 的序列化和反序列化)
This commit is contained in:
		| @@ -155,6 +155,7 @@ continew-admin  # 全局通用项目配置及依赖版本管理 | |||||||
|   │      │          ├─ mapper     # 系统管理相关 Mapper |   │      │          ├─ mapper     # 系统管理相关 Mapper | ||||||
|   │      │          ├─ model      # 系统管理相关模型 |   │      │          ├─ model      # 系统管理相关模型 | ||||||
|   │      │          │  ├─ entity      # 系统管理相关实体对象 |   │      │          │  ├─ entity      # 系统管理相关实体对象 | ||||||
|  |   │      │          │  ├─ request     # 系统管理相关请求对象 | ||||||
|   │      │          │  └─ vo          # 系统管理相关 VO(View Object) |   │      │          │  └─ vo          # 系统管理相关 VO(View Object) | ||||||
|   │      │          └─ service    # 系统管理相关业务接口及实现类 |   │      │          └─ service    # 系统管理相关业务接口及实现类 | ||||||
|   │      │             └─ impl        # 系统管理相关业务实现类 |   │      │             └─ impl        # 系统管理相关业务实现类 | ||||||
|   | |||||||
| @@ -44,12 +44,12 @@ public class BigNumberSerializer extends NumberSerializer { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException { |     public void serialize(Number value, JsonGenerator generator, SerializerProvider provider) throws IOException { | ||||||
|         // 序列化为字符串 |         // 序列化为字符串 | ||||||
|         if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) { |         if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) { | ||||||
|             super.serialize(value, gen, provider); |             super.serialize(value, generator, provider); | ||||||
|         } else { |         } else { | ||||||
|             gen.writeString(value.toString()); |             generator.writeString(value.toString()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,76 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package top.charles7c.cnadmin.common.config.jackson; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.lang.reflect.Field; | ||||||
|  |  | ||||||
|  | import com.baomidou.mybatisplus.annotation.IEnum; | ||||||
|  | import com.fasterxml.jackson.core.*; | ||||||
|  | import com.fasterxml.jackson.databind.DeserializationContext; | ||||||
|  | import com.fasterxml.jackson.databind.JsonDeserializer; | ||||||
|  | import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.util.ClassUtil; | ||||||
|  | import cn.hutool.core.util.ReflectUtil; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 通用枚举接口 IEnum 反序列化器 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/8 13:56 | ||||||
|  |  */ | ||||||
|  | @JacksonStdImpl | ||||||
|  | public class IEnumDeserializer extends JsonDeserializer<IEnum> { | ||||||
|  |  | ||||||
|  |     /** 静态实例 */ | ||||||
|  |     public static final IEnumDeserializer SERIALIZER_INSTANCE = new IEnumDeserializer(); | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public IEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { | ||||||
|  |         Class<?> targetClass = jsonParser.getCurrentValue().getClass(); | ||||||
|  |         String fieldName = jsonParser.getCurrentName(); | ||||||
|  |         String value = jsonParser.getText(); | ||||||
|  |         return this.getEnum(targetClass, value, fieldName); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 通过某字段对应值获取枚举,获取不到时为 {@code null} | ||||||
|  |      * | ||||||
|  |      * @param targetClass | ||||||
|  |      *            目标类型 | ||||||
|  |      * @param value | ||||||
|  |      *            字段值 | ||||||
|  |      * @param fieldName | ||||||
|  |      *            字段名 | ||||||
|  |      * @return 对应枚举 ,获取不到时为 {@code null} | ||||||
|  |      */ | ||||||
|  |     public IEnum getEnum(Class<?> targetClass, String value, String fieldName) { | ||||||
|  |         Field field = ReflectUtil.getField(targetClass, fieldName); | ||||||
|  |         Class<?> fieldTypeClass = field.getType(); | ||||||
|  |         Object[] enumConstants = fieldTypeClass.getEnumConstants(); | ||||||
|  |         for (Object enumConstant : enumConstants) { | ||||||
|  |             if (ClassUtil.isAssignable(IEnum.class, fieldTypeClass)) { | ||||||
|  |                 IEnum iEnum = (IEnum)enumConstant; | ||||||
|  |                 if (iEnum.getValue().equals(Integer.valueOf(value))) { | ||||||
|  |                     return iEnum; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package top.charles7c.cnadmin.common.config.jackson; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  |  | ||||||
|  | import com.baomidou.mybatisplus.annotation.IEnum; | ||||||
|  | import com.fasterxml.jackson.core.JsonGenerator; | ||||||
|  | import com.fasterxml.jackson.databind.JsonSerializer; | ||||||
|  | import com.fasterxml.jackson.databind.SerializerProvider; | ||||||
|  | import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 通用枚举接口 IEnum 序列化器 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/8 13:56 | ||||||
|  |  */ | ||||||
|  | @JacksonStdImpl | ||||||
|  | public class IEnumSerializer extends JsonSerializer<IEnum> { | ||||||
|  |  | ||||||
|  |     /** 静态实例 */ | ||||||
|  |     public static final IEnumSerializer SERIALIZER_INSTANCE = new IEnumSerializer(); | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void serialize(IEnum value, JsonGenerator generator, SerializerProvider serializers) throws IOException { | ||||||
|  |         generator.writeObject(value.getValue()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -16,7 +16,6 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.common.config.jackson; | package top.charles7c.cnadmin.common.config.jackson; | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.math.BigDecimal; | import java.math.BigDecimal; | ||||||
| import java.math.BigInteger; | import java.math.BigInteger; | ||||||
| import java.time.LocalDate; | import java.time.LocalDate; | ||||||
| @@ -30,11 +29,11 @@ import lombok.extern.slf4j.Slf4j; | |||||||
| import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; | import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; | ||||||
| import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||||
| import org.springframework.context.annotation.Configuration; | import org.springframework.context.annotation.Configuration; | ||||||
|  | import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; | ||||||
|  |  | ||||||
| import com.baomidou.mybatisplus.annotation.IEnum; | import com.baomidou.mybatisplus.annotation.IEnum; | ||||||
| import com.fasterxml.jackson.core.JsonGenerator; | import com.fasterxml.jackson.databind.*; | ||||||
| import com.fasterxml.jackson.databind.JsonSerializer; | import com.fasterxml.jackson.databind.module.SimpleModule; | ||||||
| import com.fasterxml.jackson.databind.SerializerProvider; |  | ||||||
| import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; | ||||||
| import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | ||||||
| import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; | ||||||
| @@ -55,7 +54,7 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; | |||||||
| public class JacksonConfiguration { | public class JacksonConfiguration { | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 全局配置序列化返回 JSON 处理 |      * 针对数值类型:Long、BigInteger、BigDecimal,时间类型:LocalDateTime、LocalDate、LocalTime 的序列化和反序列化 | ||||||
|      */ |      */ | ||||||
|     @Bean |     @Bean | ||||||
|     public Jackson2ObjectMapperBuilderCustomizer customizer() { |     public Jackson2ObjectMapperBuilderCustomizer customizer() { | ||||||
| @@ -64,23 +63,14 @@ public class JacksonConfiguration { | |||||||
|         String timeFormatPattern = "HH:mm:ss"; |         String timeFormatPattern = "HH:mm:ss"; | ||||||
|  |  | ||||||
|         return builder -> { |         return builder -> { | ||||||
|             // 针对通用枚举 IEnum 的转换 |             // 针对数值类型:Long、BigInteger、BigDecimal 的序列化和反序列化 | ||||||
|             builder.serializerByType(IEnum.class, new JsonSerializer<IEnum<Integer>>() { |  | ||||||
|                 @Override |  | ||||||
|                 public void serialize(IEnum<Integer> value, JsonGenerator gen, SerializerProvider serializers) |  | ||||||
|                     throws IOException { |  | ||||||
|                     gen.writeNumber(value.getValue()); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|             // 针对 Long、BigInteger、BigDecimal 的转换 |  | ||||||
|             JavaTimeModule javaTimeModule = new JavaTimeModule(); |             JavaTimeModule javaTimeModule = new JavaTimeModule(); | ||||||
|             javaTimeModule.addSerializer(Long.class, BigNumberSerializer.SERIALIZER_INSTANCE); |             javaTimeModule.addSerializer(Long.class, BigNumberSerializer.SERIALIZER_INSTANCE); | ||||||
|             javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.SERIALIZER_INSTANCE); |             javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.SERIALIZER_INSTANCE); | ||||||
|             javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.SERIALIZER_INSTANCE); |             javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.SERIALIZER_INSTANCE); | ||||||
|             javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); |             javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); | ||||||
|  |  | ||||||
|             // 针对 LocalDateTime、LocalDate、LocalTime 的转换 |             // 针对时间类型:LocalDateTime、LocalDate、LocalTime 的序列化和反序列化 | ||||||
|             DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(dateTimeFormatPattern); |             DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(dateTimeFormatPattern); | ||||||
|             javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter)); |             javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter)); | ||||||
|             javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter)); |             javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter)); | ||||||
| @@ -97,4 +87,21 @@ public class JacksonConfiguration { | |||||||
|             log.info(">>>初始化 Jackson 配置<<<"); |             log.info(">>>初始化 Jackson 配置<<<"); | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 针对通用枚举接口 IEnum 的序列化和反序列化 | ||||||
|  |      */ | ||||||
|  |     @Bean | ||||||
|  |     public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { | ||||||
|  |         SimpleModule simpleModule = new SimpleModule(); | ||||||
|  |         simpleModule.addSerializer(IEnum.class, IEnumSerializer.SERIALIZER_INSTANCE); | ||||||
|  |  | ||||||
|  |         SimpleDeserializersWrapper deserializers = new SimpleDeserializersWrapper(); | ||||||
|  |         deserializers.addDeserializer(IEnum.class, IEnumDeserializer.SERIALIZER_INSTANCE); | ||||||
|  |         simpleModule.setDeserializers(deserializers); | ||||||
|  |  | ||||||
|  |         ObjectMapper objectMapper = builder.createXmlMapper(false).build(); | ||||||
|  |         objectMapper.registerModule(simpleModule); | ||||||
|  |         return objectMapper; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,68 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package top.charles7c.cnadmin.common.config.jackson; | ||||||
|  |  | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  |  | ||||||
|  | import com.fasterxml.jackson.databind.BeanDescription; | ||||||
|  | import com.fasterxml.jackson.databind.DeserializationConfig; | ||||||
|  | import com.fasterxml.jackson.databind.JsonDeserializer; | ||||||
|  | import com.fasterxml.jackson.databind.JsonMappingException; | ||||||
|  | import com.fasterxml.jackson.databind.module.SimpleDeserializers; | ||||||
|  | import com.fasterxml.jackson.databind.type.ClassKey; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 反序列化器包装类(重写 Jackson 反序列化枚举方法,参阅:FasterXML/jackson-databind#2842) | ||||||
|  |  * | ||||||
|  |  * <p> | ||||||
|  |  * 默认处理:<br> | ||||||
|  |  * 1. Jackson 会先查找指定枚举类型对应的反序列化器(例如:GenderEnum 枚举类型,则是找 GenderEnum 枚举类型的对应反序列化器);<br> | ||||||
|  |  * 2. 如果找不到则开始查找 Enum 类型(所有枚举父类)的反序列化器;<br> | ||||||
|  |  * 3. 如果都找不到则会采用默认的枚举反序列化器(它仅能根据枚举类型的 name、ordinal 来进行反序列化)。 | ||||||
|  |  * </p> | ||||||
|  |  * <p> | ||||||
|  |  * 重写增强后:<br> | ||||||
|  |  * 1. 同默认 1;<br> | ||||||
|  |  * 2. 同默认 2;<br> | ||||||
|  |  * 3. 如果也找不到 Enum 类型(所有枚举父类)的反序列化器,开始查找指定枚举类型的接口的反序列化器(例如:GenderEnum 枚举类型,则是找它的接口 IEnum 的反序列化器);<br> | ||||||
|  |  * 4. 同默认 3。 | ||||||
|  |  * </p> | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/8 13:28 | ||||||
|  |  */ | ||||||
|  | @Slf4j | ||||||
|  | public class SimpleDeserializersWrapper extends SimpleDeserializers { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config, | ||||||
|  |         BeanDescription beanDesc) throws JsonMappingException { | ||||||
|  |         JsonDeserializer<?> deser = super.findEnumDeserializer(type, config, beanDesc); | ||||||
|  |         if (deser != null) { | ||||||
|  |             return deser; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 重写增强:开始查找指定枚举类型的接口的反序列化器(例如:GenderEnum 枚举类型,则是找它的接口 IEnum 的反序列化器) | ||||||
|  |         for (Class<?> typeInterface : type.getInterfaces()) { | ||||||
|  |             deser = this._classMappings.get(new ClassKey(typeInterface)); | ||||||
|  |             if (deser != null) { | ||||||
|  |                 return deser; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -65,7 +65,7 @@ public class UserInfoVO implements Serializable { | |||||||
|     /** |     /** | ||||||
|      * 性别(0未知 1男 2女) |      * 性别(0未知 1男 2女) | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "性别(0未知 1男 2女)") |     @Schema(description = "性别(0未知 1男 2女)", type = "Integer", allowableValues = {"0", "1", "2"}) | ||||||
|     private GenderEnum gender; |     private GenderEnum gender; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -0,0 +1,57 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package top.charles7c.cnadmin.system.model.request; | ||||||
|  |  | ||||||
|  | import java.io.Serializable; | ||||||
|  |  | ||||||
|  | import javax.validation.constraints.NotBlank; | ||||||
|  | import javax.validation.constraints.NotNull; | ||||||
|  | import javax.validation.constraints.Size; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.enums.GenderEnum; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 修改基础信息 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/7 23:08 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @Schema(description = "修改基础信息") | ||||||
|  | public class UpdateBasicInfoRequest implements Serializable { | ||||||
|  |  | ||||||
|  |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 昵称 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "昵称") | ||||||
|  |     @NotBlank(message = "昵称不能为空") | ||||||
|  |     @Size(max = 32, message = "昵称长度不能超过 32 个字符") | ||||||
|  |     private String nickname; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 性别(0未知 1男 2女) | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "性别(0未知 1男 2女)", type = "Integer", allowableValues = {"0", "1", "2"}) | ||||||
|  |     @NotNull(message = "非法性别") | ||||||
|  |     private GenderEnum gender; | ||||||
|  | } | ||||||
| @@ -16,6 +16,8 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.system.service; | package top.charles7c.cnadmin.system.service; | ||||||
|  |  | ||||||
|  | import org.springframework.web.multipart.MultipartFile; | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.system.model.entity.SysUser; | import top.charles7c.cnadmin.system.model.entity.SysUser; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -36,12 +38,21 @@ public interface UserService { | |||||||
|     SysUser getByUsername(String username); |     SysUser getByUsername(String username); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 修改头像 |      * 上传头像 | ||||||
|      * |      * | ||||||
|      * @param avatar |      * @param avatar | ||||||
|      *            头像路径 |      *            头像文件 | ||||||
|      * @param userId |      * @param userId | ||||||
|      *            用户ID |      *            用户 ID | ||||||
|  |      * @return 新头像路径 | ||||||
|      */ |      */ | ||||||
|     void updateAvatar(String avatar, Long userId); |     String uploadAvatar(MultipartFile avatar, Long userId); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 修改信息 | ||||||
|  |      * | ||||||
|  |      * @param user | ||||||
|  |      *            用户信息 | ||||||
|  |      */ | ||||||
|  |     void update(SysUser user); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,13 +16,26 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.system.service.impl; | package top.charles7c.cnadmin.system.service.impl; | ||||||
|  |  | ||||||
|  | import java.io.File; | ||||||
|  |  | ||||||
| import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||||
|  |  | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
|  | import org.springframework.transaction.annotation.Transactional; | ||||||
|  | import org.springframework.web.multipart.MultipartFile; | ||||||
|  |  | ||||||
| import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | ||||||
| import com.baomidou.mybatisplus.core.toolkit.Wrappers; | import com.baomidou.mybatisplus.core.toolkit.Wrappers; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.bean.BeanUtil; | ||||||
|  | import cn.hutool.core.io.FileUtil; | ||||||
|  | import cn.hutool.core.util.StrUtil; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.config.properties.LocalStorageProperties; | ||||||
|  | import top.charles7c.cnadmin.common.model.dto.LoginUser; | ||||||
|  | import top.charles7c.cnadmin.common.util.FileUtils; | ||||||
|  | import top.charles7c.cnadmin.common.util.helper.LoginHelper; | ||||||
|  | import top.charles7c.cnadmin.common.util.validate.CheckUtils; | ||||||
| import top.charles7c.cnadmin.system.mapper.UserMapper; | import top.charles7c.cnadmin.system.mapper.UserMapper; | ||||||
| import top.charles7c.cnadmin.system.model.entity.SysUser; | import top.charles7c.cnadmin.system.model.entity.SysUser; | ||||||
| import top.charles7c.cnadmin.system.service.UserService; | import top.charles7c.cnadmin.system.service.UserService; | ||||||
| @@ -38,6 +51,7 @@ import top.charles7c.cnadmin.system.service.UserService; | |||||||
| public class UserServiceImpl implements UserService { | public class UserServiceImpl implements UserService { | ||||||
|  |  | ||||||
|     private final UserMapper userMapper; |     private final UserMapper userMapper; | ||||||
|  |     private final LocalStorageProperties localStorageProperties; | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public SysUser getByUsername(String username) { |     public SysUser getByUsername(String username) { | ||||||
| @@ -45,8 +59,41 @@ public class UserServiceImpl implements UserService { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateAvatar(String avatar, Long userId) { |     @Transactional(rollbackFor = Exception.class) | ||||||
|  |     public String uploadAvatar(MultipartFile avatarFile, Long userId) { | ||||||
|  |         // 上传新头像 | ||||||
|  |         String avatarPath = localStorageProperties.getPath().getAvatar(); | ||||||
|  |         File newAvatarFile = FileUtils.upload(avatarFile, avatarPath, false); | ||||||
|  |         CheckUtils.exIfNull(newAvatarFile, "上传头像失败"); | ||||||
|  |         assert newAvatarFile != null; | ||||||
|  |  | ||||||
|  |         // 更新用户头像 | ||||||
|  |         String newAvatar = newAvatarFile.getName(); | ||||||
|         userMapper.update(null, |         userMapper.update(null, | ||||||
|             new LambdaUpdateWrapper<SysUser>().set(SysUser::getAvatar, avatar).eq(SysUser::getUserId, userId)); |             new LambdaUpdateWrapper<SysUser>().set(SysUser::getAvatar, newAvatar).eq(SysUser::getUserId, userId)); | ||||||
|  |  | ||||||
|  |         // 删除原头像 | ||||||
|  |         LoginUser loginUser = LoginHelper.getLoginUser(); | ||||||
|  |         String oldAvatar = loginUser.getAvatar(); | ||||||
|  |         if (StrUtil.isNotBlank(loginUser.getAvatar())) { | ||||||
|  |             FileUtil.del(avatarPath + oldAvatar); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 更新登录用户信息 | ||||||
|  |         loginUser.setAvatar(newAvatar); | ||||||
|  |         LoginHelper.updateLoginUser(loginUser); | ||||||
|  |         return newAvatar; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     @Transactional(rollbackFor = Exception.class) | ||||||
|  |     public void update(SysUser user) { | ||||||
|  |         userMapper.updateById(user); | ||||||
|  |  | ||||||
|  |         // 更新登录用户信息 | ||||||
|  |         SysUser sysUser = userMapper.selectById(user.getUserId()); | ||||||
|  |         LoginUser loginUser = LoginHelper.getLoginUser(); | ||||||
|  |         BeanUtil.copyProperties(sysUser, loginUser); | ||||||
|  |         LoginHelper.updateLoginUser(loginUser); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -38,7 +38,11 @@ axios.interceptors.request.use( | |||||||
| // add response interceptors | // add response interceptors | ||||||
| axios.interceptors.response.use( | axios.interceptors.response.use( | ||||||
|   (response: AxiosResponse<HttpResponse>) => { |   (response: AxiosResponse<HttpResponse>) => { | ||||||
|     return response.data; |     const res = response.data; | ||||||
|  |     if (!res.success) { | ||||||
|  |       Message.error(res.msg); | ||||||
|  |     } | ||||||
|  |     return res; | ||||||
|   }, |   }, | ||||||
|   (error) => { |   (error) => { | ||||||
|     const res = error.response.data; |     const res = error.response.data; | ||||||
|   | |||||||
| @@ -13,6 +13,10 @@ export function uploadAvatar(data: FormData) { | |||||||
|   return axios.post<AvatarRes>('/system/user/center/avatar', data); |   return axios.post<AvatarRes>('/system/user/center/avatar', data); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function saveUserInfo() { | export interface UpdateBasicInfoReq { | ||||||
|   return axios.get('/api/user/save-info'); |   nickname: string; | ||||||
|  |   gender: number; | ||||||
|  | } | ||||||
|  | export function updateBasicInfo(req: UpdateBasicInfoReq) { | ||||||
|  |   return axios.patch('/system/user/center/basic/info', req); | ||||||
| } | } | ||||||
| @@ -19,6 +19,8 @@ | |||||||
|         <a-input |         <a-input | ||||||
|           v-model="loginForm.username" |           v-model="loginForm.username" | ||||||
|           :placeholder="$t('login.form.username.placeholder')" |           :placeholder="$t('login.form.username.placeholder')" | ||||||
|  |           size="large" | ||||||
|  |           max-length="50" | ||||||
|         > |         > | ||||||
|           <template #prefix> |           <template #prefix> | ||||||
|             <icon-user /> |             <icon-user /> | ||||||
| @@ -34,7 +36,9 @@ | |||||||
|         <a-input-password |         <a-input-password | ||||||
|           v-model="loginForm.password" |           v-model="loginForm.password" | ||||||
|           :placeholder="$t('login.form.password.placeholder')" |           :placeholder="$t('login.form.password.placeholder')" | ||||||
|  |           size="large" | ||||||
|           allow-clear |           allow-clear | ||||||
|  |           max-length="50" | ||||||
|         > |         > | ||||||
|           <template #prefix> |           <template #prefix> | ||||||
|             <icon-lock /> |             <icon-lock /> | ||||||
| @@ -51,6 +55,7 @@ | |||||||
|         <a-input |         <a-input | ||||||
|           v-model="loginForm.captcha" |           v-model="loginForm.captcha" | ||||||
|           :placeholder="$t('login.form.captcha.placeholder')" |           :placeholder="$t('login.form.captcha.placeholder')" | ||||||
|  |           size="large" | ||||||
|           style="width: 63%" |           style="width: 63%" | ||||||
|           allow-clear |           allow-clear | ||||||
|         > |         > | ||||||
| @@ -70,7 +75,7 @@ | |||||||
|             {{ $t('login.form.rememberMe') }} |             {{ $t('login.form.rememberMe') }} | ||||||
|           </a-checkbox> |           </a-checkbox> | ||||||
|         </div> |         </div> | ||||||
|         <a-button type="primary" html-type="submit" long :loading="loading"> |         <a-button type="primary" size="large" html-type="submit" long :loading="loading"> | ||||||
|           {{ $t('login.form.login') }} |           {{ $t('login.form.login') }} | ||||||
|         </a-button> |         </a-button> | ||||||
|       </a-space> |       </a-space> | ||||||
|   | |||||||
| @@ -1,18 +1,19 @@ | |||||||
| export default { | export default { | ||||||
|   'login.form.title': 'Login to ContiNew Admin', |   'login.form.title': 'Login to ContiNew Admin', | ||||||
|   'login.form.subTitle': 'Continue to build the latest popular technology stack in the background management framework', |   'login.form.subTitle': 'Continue to build the latest popular technology stack in the background management framework', | ||||||
|   'login.form.username.errMsg': 'Username cannot be empty', |   'login.form.username.placeholder': 'Please enter username', | ||||||
|   'login.form.password.errMsg': 'Password cannot be empty', |   'login.form.username.errMsg': 'Please enter username', | ||||||
|   'login.form.captcha.errMsg': 'Captcha cannot be empty', |   'login.form.password.placeholder': 'Please enter password', | ||||||
|   'login.form.login.errMsg': 'Login error, refresh and try again', |   'login.form.password.errMsg': 'Please enter password', | ||||||
|   'login.form.login.success': 'welcome to use', |   'login.form.captcha.placeholder': 'Please enter captcha', | ||||||
|   'login.form.username.placeholder': 'Username', |   'login.form.captcha.errMsg': 'Please enter captcha', | ||||||
|   'login.form.password.placeholder': 'Password', |   'login.form.rememberMe': 'Remember me', | ||||||
|   'login.form.captcha.placeholder': 'Captcha', |   'login.form.login': 'Login', | ||||||
|   'login.form.rememberMe': 'Remember Me', |   'login.form.register': 'Register account', | ||||||
|   'login.form.forgetPassword': 'Forgot password', |   'login.form.forgetPassword': 'Forgot password', | ||||||
|   'login.form.login': 'login', |   'login.form.login.success': 'Welcome to use', | ||||||
|   'login.form.register': 'register account', |   'login.form.login.errMsg': 'Login error, refresh and try again', | ||||||
|  |  | ||||||
|   'login.banner.slogan1': 'Out-of-the-box high-quality template', |   'login.banner.slogan1': 'Out-of-the-box high-quality template', | ||||||
|   'login.banner.subSlogan1': |   'login.banner.subSlogan1': | ||||||
|     'Rich page templates, covering most typical business scenarios', |     'Rich page templates, covering most typical business scenarios', | ||||||
|   | |||||||
| @@ -1,18 +1,19 @@ | |||||||
| export default { | export default { | ||||||
|   'login.form.title': '登录 ContiNew Admin', |   'login.form.title': '登录 ContiNew Admin', | ||||||
|   'login.form.subTitle': '持续以最新流行技术栈构建的中后台管理框架', |   'login.form.subTitle': '持续以最新流行技术栈构建的中后台管理框架', | ||||||
|   'login.form.username.errMsg': '用户名不能为空', |   'login.form.username.placeholder': '请输入用户名', | ||||||
|   'login.form.password.errMsg': '密码不能为空', |   'login.form.username.errMsg': '请输入用户名', | ||||||
|   'login.form.captcha.errMsg': '验证码不能为空', |   'login.form.password.placeholder': '请输入密码', | ||||||
|   'login.form.login.errMsg': '登录出错,请刷新重试', |   'login.form.password.errMsg': '请输入密码', | ||||||
|   'login.form.login.success': '欢迎使用', |   'login.form.captcha.placeholder': '请输入验证码', | ||||||
|   'login.form.username.placeholder': '用户名', |   'login.form.captcha.errMsg': '请输入验证码', | ||||||
|   'login.form.password.placeholder': '密码', |  | ||||||
|   'login.form.captcha.placeholder': '验证码', |  | ||||||
|   'login.form.rememberMe': '记住我', |   'login.form.rememberMe': '记住我', | ||||||
|   'login.form.forgetPassword': '忘记密码', |  | ||||||
|   'login.form.login': '登录', |   'login.form.login': '登录', | ||||||
|   'login.form.register': '注册账号', |   'login.form.register': '注册账号', | ||||||
|  |   'login.form.forgetPassword': '忘记密码', | ||||||
|  |   'login.form.login.success': '欢迎使用', | ||||||
|  |   'login.form.login.errMsg': '登录出错,请刷新重试', | ||||||
|  |  | ||||||
|   'login.banner.slogan1': '中后台管理框架', |   'login.banner.slogan1': '中后台管理框架', | ||||||
|   'login.banner.subSlogan1': 'Continue New Admin,持续以最新流行技术栈构建', |   'login.banner.subSlogan1': 'Continue New Admin,持续以最新流行技术栈构建', | ||||||
|   'login.banner.slogan2': '内置了常见问题的解决方案', |   'login.banner.slogan2': '内置了常见问题的解决方案', | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ | |||||||
|       <a-input |       <a-input | ||||||
|         v-model="formData.username" |         v-model="formData.username" | ||||||
|         :placeholder="$t('userCenter.basicInfo.placeholder.username')" |         :placeholder="$t('userCenter.basicInfo.placeholder.username')" | ||||||
|  |         max-length="50" | ||||||
|       /> |       /> | ||||||
|     </a-form-item> |     </a-form-item> | ||||||
|     <a-form-item |     <a-form-item | ||||||
| @@ -34,6 +35,7 @@ | |||||||
|       <a-input |       <a-input | ||||||
|         v-model="formData.nickname" |         v-model="formData.nickname" | ||||||
|         :placeholder="$t('userCenter.basicInfo.placeholder.nickname')" |         :placeholder="$t('userCenter.basicInfo.placeholder.nickname')" | ||||||
|  |         max-length="32" | ||||||
|       /> |       /> | ||||||
|     </a-form-item> |     </a-form-item> | ||||||
|     <a-form-item |     <a-form-item | ||||||
| @@ -48,7 +50,7 @@ | |||||||
|     </a-form-item> |     </a-form-item> | ||||||
|     <a-form-item> |     <a-form-item> | ||||||
|       <a-space> |       <a-space> | ||||||
|         <a-button type="primary" @click="validate"> |         <a-button type="primary" :loading="loading" @click="save"> | ||||||
|           {{ $t('userCenter.save') }} |           {{ $t('userCenter.save') }} | ||||||
|         </a-button> |         </a-button> | ||||||
|         <a-button type="secondary" @click="reset"> |         <a-button type="secondary" @click="reset"> | ||||||
| @@ -62,9 +64,13 @@ | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|   import { ref } from 'vue'; |   import { ref } from 'vue'; | ||||||
|   import { useLoginStore } from '@/store'; |   import { useLoginStore } from '@/store'; | ||||||
|  |   import { updateBasicInfo } from '@/api/system/user-center'; | ||||||
|  |   import useLoading from '@/hooks/loading'; | ||||||
|   import { FormInstance } from '@arco-design/web-vue/es/form'; |   import { FormInstance } from '@arco-design/web-vue/es/form'; | ||||||
|   import { BasicInfoModel } from '@/api/system/user-center'; |   import { BasicInfoModel } from '@/api/system/user-center'; | ||||||
|  |   import { Message } from "@arco-design/web-vue"; | ||||||
|  |  | ||||||
|  |   const { loading, setLoading } = useLoading(); | ||||||
|   const loginStore = useLoginStore(); |   const loginStore = useLoginStore(); | ||||||
|   const formRef = ref<FormInstance>(); |   const formRef = ref<FormInstance>(); | ||||||
|   const formData = ref<BasicInfoModel>({ |   const formData = ref<BasicInfoModel>({ | ||||||
| @@ -74,11 +80,21 @@ | |||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   // 保存 |   // 保存 | ||||||
|   const validate = async () => { |   const save = async () => { | ||||||
|     const res = await formRef.value?.validate(); |     const errors = await formRef.value?.validate(); | ||||||
|     if (!res) { |     if (loading.value) return; | ||||||
|       // do some thing |     if (!errors) { | ||||||
|       // you also can use html-type to submit |       setLoading(true); | ||||||
|  |       try { | ||||||
|  |         const res = await updateBasicInfo({ | ||||||
|  |           nickname: formData.value.nickname, | ||||||
|  |           gender: formData.value.gender, | ||||||
|  |         }); | ||||||
|  |         await loginStore.getInfo(); | ||||||
|  |         if (res.success) Message.success(res.msg); | ||||||
|  |       } finally { | ||||||
|  |         setLoading(false); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,7 +19,6 @@ | |||||||
|         </template> |         </template> | ||||||
|       </a-upload> |       </a-upload> | ||||||
|       <a-descriptions |       <a-descriptions | ||||||
|         :data="renderData" |  | ||||||
|         :column="2" |         :column="2" | ||||||
|         align="right" |         align="right" | ||||||
|         layout="inline-horizontal" |         layout="inline-horizontal" | ||||||
| @@ -34,21 +33,21 @@ | |||||||
|           textAlign: 'left', |           textAlign: 'left', | ||||||
|         }" |         }" | ||||||
|       > |       > | ||||||
|         <template #label="{ label }">{{ $t(label) }} :</template> |         <a-descriptions-item :label="$t('userCenter.label.nickname')">{{ loginStore.nickname }}</a-descriptions-item> | ||||||
|         <template #value="{ value, data }"> |         <a-descriptions-item :label="$t('userCenter.label.gender')"> | ||||||
|           <div v-if="data.label === 'userCenter.label.gender'"> |           <div v-if="loginStore.gender === 1"> | ||||||
|             <div v-if="loginStore.gender === 1"> |             男 | ||||||
|               男 |             <icon-man style="color: #19BBF1" /> | ||||||
|               <icon-man style="color: #19BBF1" /> |  | ||||||
|             </div> |  | ||||||
|             <div v-else-if="loginStore.gender === 2"> |  | ||||||
|               女 |  | ||||||
|               <icon-woman style="color: #FA7FA9" /> |  | ||||||
|             </div> |  | ||||||
|             <div v-else>未知</div> |  | ||||||
|           </div> |           </div> | ||||||
|           <span v-else>{{ value }}</span> |           <div v-else-if="loginStore.gender === 2"> | ||||||
|         </template> |             女 | ||||||
|  |             <icon-woman style="color: #FA7FA9" /> | ||||||
|  |           </div> | ||||||
|  |           <div v-else>未知</div> | ||||||
|  |         </a-descriptions-item> | ||||||
|  |         <a-descriptions-item :label="$t('userCenter.label.phone')">{{ loginStore.phone }}</a-descriptions-item> | ||||||
|  |         <a-descriptions-item :label="$t('userCenter.label.email')">{{ loginStore.email }}</a-descriptions-item> | ||||||
|  |         <a-descriptions-item :label="$t('userCenter.label.registrationDate')">{{ loginStore.registrationDate }}</a-descriptions-item> | ||||||
|       </a-descriptions> |       </a-descriptions> | ||||||
|     </a-space> |     </a-space> | ||||||
|   </a-card> |   </a-card> | ||||||
| @@ -62,7 +61,6 @@ | |||||||
|   } from '@arco-design/web-vue/es/upload/interfaces'; |   } from '@arco-design/web-vue/es/upload/interfaces'; | ||||||
|   import { useLoginStore } from '@/store'; |   import { useLoginStore } from '@/store'; | ||||||
|   import { uploadAvatar } from '@/api/system/user-center'; |   import { uploadAvatar } from '@/api/system/user-center'; | ||||||
|   import type { DescData } from '@arco-design/web-vue/es/descriptions/interface'; |  | ||||||
|   import getAvatar from "@/utils/avatar"; |   import getAvatar from "@/utils/avatar"; | ||||||
|   import { Message } from "@arco-design/web-vue"; |   import { Message } from "@arco-design/web-vue"; | ||||||
|  |  | ||||||
| @@ -72,28 +70,6 @@ | |||||||
|     name: 'avatar.png', |     name: 'avatar.png', | ||||||
|     url: getAvatar(loginStore), |     url: getAvatar(loginStore), | ||||||
|   }; |   }; | ||||||
|   const renderData = [ |  | ||||||
|     { |  | ||||||
|       label: 'userCenter.label.nickname', |  | ||||||
|       value: loginStore.nickname, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       label: 'userCenter.label.gender', |  | ||||||
|       value: loginStore.gender, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       label: 'userCenter.label.phone', |  | ||||||
|       value: loginStore.phone, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       label: 'userCenter.label.email', |  | ||||||
|       value: loginStore.email, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       label: 'userCenter.label.registrationDate', |  | ||||||
|       value: loginStore.registrationDate, |  | ||||||
|     }, |  | ||||||
|   ] as DescData[]; |  | ||||||
|   const avatarList = ref<FileItem[]>([avatar]); |   const avatarList = ref<FileItem[]>([avatar]); | ||||||
|  |  | ||||||
|   // 切换头像 |   // 切换头像 | ||||||
| @@ -118,10 +94,7 @@ | |||||||
|       try { |       try { | ||||||
|         const res = await uploadAvatar(formData); |         const res = await uploadAvatar(formData); | ||||||
|         onSuccess(res); |         onSuccess(res); | ||||||
|         Message.success({ |         if (res.success) Message.success(res.msg); | ||||||
|           content: res.msg || '网络错误', |  | ||||||
|           duration: 3 * 1000, |  | ||||||
|         }); |  | ||||||
|         // 更换头像 |         // 更换头像 | ||||||
|         loginStore.avatar = res.data.avatar; |         loginStore.avatar = res.data.avatar; | ||||||
|       } catch (error) { |       } catch (error) { | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| export default { | export default { | ||||||
|   'menu.user.center': 'User Center', |   'menu.user.center': 'User Center', | ||||||
|   'userCenter.label.nickname': 'Nick Name', |   'userCenter.label.nickname': 'Nick Name :', | ||||||
|   'userCenter.label.gender': 'Gender', |   'userCenter.label.gender': 'Gender :', | ||||||
|   'userCenter.label.phone': 'Phone', |   'userCenter.label.phone': 'Phone :', | ||||||
|   'userCenter.label.email': 'Email', |   'userCenter.label.email': 'Email :', | ||||||
|   'userCenter.label.registrationDate': 'Registration Date', |   'userCenter.label.registrationDate': 'Registration Date :', | ||||||
|  |  | ||||||
|   'userCenter.tab.basicInformation': 'Basic Information', |   'userCenter.tab.basicInformation': 'Basic Information', | ||||||
|   'userCenter.basicInfo.form.label.username': 'Username', |   'userCenter.basicInfo.form.label.username': 'Username', | ||||||
|   | |||||||
| @@ -1,17 +1,17 @@ | |||||||
| export default { | export default { | ||||||
|   'menu.user.center': '个人中心', |   'menu.user.center': '个人中心', | ||||||
|   'userCenter.label.nickname': '昵称', |   'userCenter.label.nickname': '昵称 :', | ||||||
|   'userCenter.label.gender': '性别', |   'userCenter.label.gender': '性别 :', | ||||||
|   'userCenter.label.phone': '手机号码', |   'userCenter.label.phone': '手机号码 :', | ||||||
|   'userCenter.label.email': '邮箱', |   'userCenter.label.email': '邮箱 :', | ||||||
|   'userCenter.label.registrationDate': '注册日期', |   'userCenter.label.registrationDate': '注册日期 :', | ||||||
|  |  | ||||||
|   'userCenter.tab.basicInformation': '基础信息', |   'userCenter.tab.basicInformation': '基础信息', | ||||||
|   'userCenter.basicInfo.form.label.username': '用户名', |   'userCenter.basicInfo.form.label.username': '用户名', | ||||||
|   'userCenter.basicInfo.placeholder.username': '请输入您的用户名', |   'userCenter.basicInfo.placeholder.username': '请输入用户名', | ||||||
|   'userCenter.form.error.username.required': '请输入用户名', |   'userCenter.form.error.username.required': '请输入用户名', | ||||||
|   'userCenter.basicInfo.form.label.nickname': '昵称', |   'userCenter.basicInfo.form.label.nickname': '昵称', | ||||||
|   'userCenter.basicInfo.placeholder.nickname': '请输入您的昵称', |   'userCenter.basicInfo.placeholder.nickname': '请输入昵称', | ||||||
|   'userCenter.form.error.nickname.required': '请输入昵称', |   'userCenter.form.error.nickname.required': '请输入昵称', | ||||||
|   'userCenter.basicInfo.form.label.gender': '性别', |   'userCenter.basicInfo.form.label.gender': '性别', | ||||||
|   'userCenter.save': '保存', |   'userCenter.save': '保存', | ||||||
|   | |||||||
| @@ -16,8 +16,6 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.webapi.controller.system; | package top.charles7c.cnadmin.webapi.controller.system; | ||||||
|  |  | ||||||
| import java.io.File; |  | ||||||
|  |  | ||||||
| import javax.validation.constraints.NotNull; | import javax.validation.constraints.NotNull; | ||||||
|  |  | ||||||
| import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||||
| @@ -27,23 +25,20 @@ import io.swagger.v3.oas.annotations.tags.Tag; | |||||||
|  |  | ||||||
| import org.springframework.http.MediaType; | import org.springframework.http.MediaType; | ||||||
| import org.springframework.validation.annotation.Validated; | import org.springframework.validation.annotation.Validated; | ||||||
| import org.springframework.web.bind.annotation.PostMapping; | import org.springframework.web.bind.annotation.*; | ||||||
| import org.springframework.web.bind.annotation.RequestMapping; |  | ||||||
| import org.springframework.web.bind.annotation.RestController; |  | ||||||
| import org.springframework.web.multipart.MultipartFile; | import org.springframework.web.multipart.MultipartFile; | ||||||
|  |  | ||||||
| import cn.hutool.core.io.FileUtil; | import cn.hutool.core.bean.BeanUtil; | ||||||
| import cn.hutool.core.io.file.FileNameUtil; | import cn.hutool.core.io.file.FileNameUtil; | ||||||
| import cn.hutool.core.util.StrUtil; | import cn.hutool.core.util.StrUtil; | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.common.config.properties.LocalStorageProperties; | import top.charles7c.cnadmin.common.config.properties.LocalStorageProperties; | ||||||
| import top.charles7c.cnadmin.common.consts.FileConstants; | import top.charles7c.cnadmin.common.consts.FileConstants; | ||||||
| import top.charles7c.cnadmin.common.model.dto.LoginUser; |  | ||||||
| import top.charles7c.cnadmin.common.model.vo.R; | import top.charles7c.cnadmin.common.model.vo.R; | ||||||
| import top.charles7c.cnadmin.common.util.FileUtils; |  | ||||||
| import top.charles7c.cnadmin.common.util.helper.LoginHelper; | import top.charles7c.cnadmin.common.util.helper.LoginHelper; | ||||||
| import top.charles7c.cnadmin.common.util.validate.CheckUtils; |  | ||||||
| import top.charles7c.cnadmin.common.util.validate.ValidationUtils; | import top.charles7c.cnadmin.common.util.validate.ValidationUtils; | ||||||
|  | import top.charles7c.cnadmin.system.model.entity.SysUser; | ||||||
|  | import top.charles7c.cnadmin.system.model.request.UpdateBasicInfoRequest; | ||||||
| import top.charles7c.cnadmin.system.model.vo.AvatarVO; | import top.charles7c.cnadmin.system.model.vo.AvatarVO; | ||||||
| import top.charles7c.cnadmin.system.service.UserService; | import top.charles7c.cnadmin.system.service.UserService; | ||||||
|  |  | ||||||
| @@ -76,25 +71,18 @@ public class UserCenterController { | |||||||
|         ValidationUtils.exIfCondition(() -> !StrUtil.equalsAnyIgnoreCase(avatarImageType, avatarSupportImgTypes), |         ValidationUtils.exIfCondition(() -> !StrUtil.equalsAnyIgnoreCase(avatarImageType, avatarSupportImgTypes), | ||||||
|             String.format("头像仅支持 %s 格式的图片", String.join(",", avatarSupportImgTypes))); |             String.format("头像仅支持 %s 格式的图片", String.join(",", avatarSupportImgTypes))); | ||||||
|  |  | ||||||
|         // 上传新头像 |         // 上传头像 | ||||||
|         String avatarPath = localStorageProperties.getPath().getAvatar(); |         String newAvatar = userService.uploadAvatar(avatarFile, LoginHelper.getUserId()); | ||||||
|         File newAvatarFile = FileUtils.upload(avatarFile, avatarPath, false); |  | ||||||
|         CheckUtils.exIfNull(newAvatarFile, "上传头像失败"); |  | ||||||
|  |  | ||||||
|         // 更新用户头像 |  | ||||||
|         LoginUser loginUser = LoginHelper.getLoginUser(); |  | ||||||
|         String newAvatar = newAvatarFile.getName(); |  | ||||||
|         userService.updateAvatar(newAvatar, loginUser.getUserId()); |  | ||||||
|  |  | ||||||
|         // 删除原头像 |  | ||||||
|         String oldAvatar = loginUser.getAvatar(); |  | ||||||
|         if (StrUtil.isNotBlank(loginUser.getAvatar())) { |  | ||||||
|             FileUtil.del(avatarPath + oldAvatar); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // 更新登录用户信息 |  | ||||||
|         loginUser.setAvatar(newAvatar); |  | ||||||
|         LoginHelper.updateLoginUser(loginUser); |  | ||||||
|         return R.ok("上传成功", new AvatarVO().setAvatar(newAvatar)); |         return R.ok("上传成功", new AvatarVO().setAvatar(newAvatar)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Operation(summary = "修改基础信息", description = "修改用户基础信息") | ||||||
|  |     @PatchMapping("/basic/info") | ||||||
|  |     public R updateBasicInfo(@Validated @RequestBody UpdateBasicInfoRequest updateBasicInfoRequest) { | ||||||
|  |         SysUser user = new SysUser(); | ||||||
|  |         user.setUserId(LoginHelper.getUserId()); | ||||||
|  |         BeanUtil.copyProperties(updateBasicInfoRequest, user); | ||||||
|  |         userService.update(user); | ||||||
|  |         return R.ok("修改成功"); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -167,11 +167,11 @@ spring: | |||||||
|     # 序列化配置(Bean -> JSON) |     # 序列化配置(Bean -> JSON) | ||||||
|     serialization: |     serialization: | ||||||
|       # 允许序列化无属性的 Bean |       # 允许序列化无属性的 Bean | ||||||
|       fail_on_empty_beans: false |       FAIL_ON_EMPTY_BEANS: false | ||||||
|     # 反序列化配置(JSON -> Bean) |     # 反序列化配置(JSON -> Bean) | ||||||
|     deserialization: |     deserialization: | ||||||
|       # 允许反序列化不存在的属性 |       # 允许反序列化不存在的属性 | ||||||
|       fail_on_unknown_properties: false |       FAIL_ON_UNKNOWN_PROPERTIES: false | ||||||
|  |  | ||||||
| --- ### 线程池配置 | --- ### 线程池配置 | ||||||
| thread-pool: | thread-pool: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user