Jelajahi Sumber

新增接口,用户个性化设置相关代码

zhangchen 1 Minggu lalu
induk
melakukan
e07d36fe8c

+ 97 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeUserPersonalizationSetting.java

@@ -0,0 +1,97 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * life_user 个性化设置
+ */
+@Data
+@JsonInclude
+@TableName("life_user_personalization_setting")
+@ApiModel(value = "LifeUserPersonalizationSetting", description = "用户个性化设置")
+public class LifeUserPersonalizationSetting implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "用户id,对应 life_user.id")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "个性化推荐 0不推荐 1推荐")
+    @TableField("personalized_recommendation")
+    private Integer personalizedRecommendation;
+
+    @ApiModelProperty(value = "允许关注的人评论我 0不允许 1允许")
+    @TableField("only_followers_comment")
+    private Integer onlyFollowersComment;
+
+    @ApiModelProperty(value = "粉丝列表 0不可见 1可见")
+    @TableField("hide_fans_list")
+    private Integer hideFansList;
+
+    @ApiModelProperty(value = "关注列表 0不可见 1可见")
+    @TableField("hide_follow_list")
+    private Integer hideFollowList;
+
+    @ApiModelProperty(value = "接收消息 0不接收 1可接收")
+    @TableField("notify_receive_message")
+    private Integer notifyReceiveMessage;
+
+    @ApiModelProperty(value = "点赞 0不可点赞 1可点赞")
+    @TableField("notify_like")
+    private Integer notifyLike;
+
+    @ApiModelProperty(value = "关注 0不可关注 1可关注")
+    @TableField("notify_follow")
+    private Integer notifyFollow;
+
+    @ApiModelProperty(value = "评论 0不可评论 1可评论")
+    @TableField("notify_comment")
+    private Integer notifyComment;
+
+    @ApiModelProperty(value = "是否跟随系统字体 0不跟随 1跟随;为1时不更新 chatFontLevel")
+    @TableField("follow_system_font")
+    private Integer followSystemFont;
+
+    @ApiModelProperty(value = "字体档位 0~4,仅 followSystemFont=0 时可调整")
+    @TableField("chat_font_level")
+    private Integer chatFontLevel;
+
+    @ApiModelProperty(value = "允许自动刷新 0不允许 1允许")
+    @TableField("auto_refresh")
+    private Integer autoRefresh;
+
+    @ApiModelProperty(value = "删除标记 0未删除 1已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}

+ 69 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/MyMergedReviewItemVo.java

@@ -0,0 +1,69 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 「我的评价」融合列表单项:门店评价 + 律师订单评价
+ */
+@Data
+@ApiModel(value = "MyMergedReviewItemVo", description = "我的评价融合列表项")
+public class MyMergedReviewItemVo {
+
+    @ApiModelProperty(value = "类型:STORE-门店评价,LAWYER-律师订单评价", required = true)
+    private String itemType;
+
+    @ApiModelProperty(value = "门店评价主键(itemType=STORE 时有值)")
+    private Long storeRatingId;
+
+    @ApiModelProperty(value = "律师评价主键(itemType=LAWYER 时有值)")
+    private Integer lawyerReviewId;
+
+    @ApiModelProperty(value = "发布时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date publishTime;
+
+    @ApiModelProperty(value = "总评分/核心评分")
+    private Double score;
+
+    @ApiModelProperty(value = "文字内容")
+    private String content;
+
+    @ApiModelProperty(value = "图片/视频等媒体 URL 列表")
+    private List<String> mediaUrls;
+
+    @ApiModelProperty(value = "门店 id(STORE)")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "门店名称(STORE)")
+    private String storeName;
+
+    @ApiModelProperty(value = "门店图标/头图(STORE,可能为空)")
+    private String storeIcon;
+
+    @ApiModelProperty(value = "律师姓名(LAWYER)")
+    private String lawyerName;
+
+    @ApiModelProperty(value = "律师头像(LAWYER)")
+    private String lawyerAvatar;
+
+    @ApiModelProperty(value = "律所名称(LAWYER)")
+    private String lawyerFirm;
+
+    @ApiModelProperty(value = "评价标签文案,如「我给出超赞」(LAWYER)")
+    private String ratingTag;
+
+    @ApiModelProperty(value = "律师用户 id(LAWYER)")
+    private Integer lawyerUserId;
+
+    @ApiModelProperty(value = "关联订单 id(LAWYER)")
+    private Integer orderId;
+
+    @ApiModelProperty(value = "门店评价业务类型 common_rating.business_type(STORE)")
+    private Integer businessType;
+}

+ 7 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeUserPersonalizationSettingMapper.java

@@ -0,0 +1,7 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import shop.alien.entity.store.LifeUserPersonalizationSetting;
+
+public interface LifeUserPersonalizationSettingMapper extends BaseMapper<LifeUserPersonalizationSetting> {
+}

+ 24 - 0
alien-store/src/main/java/shop/alien/store/controller/CommonRatingController.java

@@ -5,7 +5,9 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import shop.alien.entity.store.CommonRating;
+import shop.alien.entity.store.vo.MyMergedReviewItemVo;
 import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.service.CommonCommentService;
 import shop.alien.store.service.CommonRatingService;
@@ -190,4 +192,26 @@ public class CommonRatingController {
         return commonRatingService.getMyRatingList(pageNum, pageSize, businessType, userId, auditStatus);
     }
 
+    @ApiOperation("融合查询我的评价(门店 common_rating + 律师订单评价),按发布时间倒序")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "当前页码", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "每页条数", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "businessType", value = "门店侧业务类型,默认1-门店评价;与 getMyRatingList 一致", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "Long", paramType = "query", required = true),
+            @ApiImplicitParam(name = "auditStatus", value = "门店评价审核状态筛选,可选", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "currentUserId", value = "当前用户ID(律师评价列表判断是否已点赞),可选", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getMyMergedRatingList")
+    public R<IPage<MyMergedReviewItemVo>> getMyMergedRatingList(
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "10") Integer pageSize,
+            @RequestParam(required = false) Integer businessType,
+            @RequestParam Long userId,
+            @RequestParam(required = false) Integer auditStatus,
+            @RequestParam(required = false) Integer currentUserId) {
+        log.info("CommonRatingController.getMyMergedRatingList?pageNum={}&pageSize={}&businessType={}&userId={}&auditStatus={}&currentUserId={}",
+                pageNum, pageSize, businessType, userId, auditStatus, currentUserId);
+        return commonRatingService.getMyMergedRatingList(pageNum, pageSize, businessType, userId, auditStatus, currentUserId);
+    }
+
 }

+ 84 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeUserPersonalizationSettingController.java

@@ -0,0 +1,84 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeUserPersonalizationSetting;
+import shop.alien.store.service.LifeUserPersonalizationSettingService;
+
+@Api(tags = {"用户个性化设置"})
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/lifeUserPersonalizationSetting")
+@RequiredArgsConstructor
+public class LifeUserPersonalizationSettingController {
+
+    private final LifeUserPersonalizationSettingService lifeUserPersonalizationSettingService;
+
+    @ApiOperation("新增个性化设置")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/add")
+    public R<String> add(@RequestBody LifeUserPersonalizationSetting setting) {
+        return lifeUserPersonalizationSettingService.add(setting);
+    }
+
+    @ApiOperation("根据主键删除(逻辑删除)")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/deleteById")
+    public R<String> deleteById(@RequestParam Integer id) {
+        return lifeUserPersonalizationSettingService.deleteById(id);
+    }
+
+    @ApiOperation("更新个性化设置;传 id 或传 userId 均可定位记录(无 userId 对应记录时会先插入默认再更新)")
+    @ApiOperationSupport(order = 3)
+    @PostMapping("/update")
+    public R<String> update(@RequestBody LifeUserPersonalizationSetting setting) {
+        return lifeUserPersonalizationSettingService.update(setting);
+    }
+
+    @ApiOperation("根据主键查询")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "主键ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getById")
+    public R<LifeUserPersonalizationSetting> getById(@RequestParam Integer id) {
+        return lifeUserPersonalizationSettingService.getInfoById(id);
+    }
+
+    @ApiOperation("根据用户ID查询;若无记录则自动插入一条默认值后返回")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getByUserId")
+    public R<LifeUserPersonalizationSetting> getByUserId(@RequestParam Integer userId) {
+        return lifeUserPersonalizationSettingService.getByUserId(userId);
+    }
+
+    @ApiOperation("分页列表,可按 userId 筛选")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "每页数量", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/list")
+    public R<IPage<LifeUserPersonalizationSetting>> list(
+            @RequestParam Integer pageNum,
+            @RequestParam Integer pageSize,
+            @RequestParam(required = false) Integer userId) {
+        return lifeUserPersonalizationSettingService.list(pageNum, pageSize, userId);
+    }
+}

+ 8 - 0
alien-store/src/main/java/shop/alien/store/service/CommonRatingService.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.CommonRating;
 import shop.alien.entity.store.vo.CommonRatingVo;
+import shop.alien.entity.store.vo.MyMergedReviewItemVo;
 
 /**
  * 评价表 服务类
@@ -74,6 +75,13 @@ public interface CommonRatingService extends IService<CommonRating> {
      */
     R getMyRatingList(Integer pageNum, Integer pageSize, Integer businessType, Long userId, Integer auditStatus);
 
+    /**
+     * 融合「门店我的评价」与「律师订单我的评价」,按发布时间倒序分页。
+     * 分页说明:从两侧各取至多 pageNum*pageSize 条(上限 1000)合并后再截取当前页。
+     */
+    R<IPage<MyMergedReviewItemVo>> getMyMergedRatingList(Integer pageNum, Integer pageSize, Integer businessType,
+                                                         Long userId, Integer auditStatus, Integer currentUserId);
+
     R<IPage<CommonRatingVo>> doListBusinessWithType(IPage<CommonRating> page2, Integer i, Long userId, Integer replyStatus);
 
   /*  /**

+ 21 - 0
alien-store/src/main/java/shop/alien/store/service/LifeUserPersonalizationSettingService.java

@@ -0,0 +1,21 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeUserPersonalizationSetting;
+
+public interface LifeUserPersonalizationSettingService extends IService<LifeUserPersonalizationSetting> {
+
+    R<String> add(LifeUserPersonalizationSetting setting);
+
+    R<String> deleteById(Integer id);
+
+    R<String> update(LifeUserPersonalizationSetting setting);
+
+    R<LifeUserPersonalizationSetting> getInfoById(Integer id);
+
+    R<LifeUserPersonalizationSetting> getByUserId(Integer userId);
+
+    R<IPage<LifeUserPersonalizationSetting>> list(Integer pageNum, Integer pageSize, Integer userId);
+}

+ 119 - 0
alien-store/src/main/java/shop/alien/store/service/impl/CommonRatingServiceImpl.java

@@ -22,6 +22,8 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.vo.CommonCommentVo;
 import shop.alien.entity.store.vo.CommonRatingVo;
+import shop.alien.entity.store.vo.MyMergedReviewItemVo;
+import shop.alien.entity.store.vo.OrderReviewVo;
 import shop.alien.entity.store.vo.StoreInfoScoreVo;
 import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.entity.storePlatform.StoreOperationalActivity;
@@ -31,6 +33,7 @@ import shop.alien.util.common.Constants;
 import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.service.CommonCommentService;
 import shop.alien.store.service.CommonRatingService;
+import shop.alien.store.service.OrderReviewService;
 import shop.alien.store.service.LifeDiscountCouponStoreFriendService;
 import shop.alien.store.util.CommonConstant;
 import shop.alien.store.util.ai.AiContentModerationUtil;
@@ -88,6 +91,7 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
     private final StoreOperationalActivityMapper storeOperationalActivityMapper;
     private final ReceiptAuditUtil receiptAuditUtil;
     private final StoreClockInMapper storeClockInMapper;
+    private final OrderReviewService orderReviewService;
 
     public static final List<String> SERVICES_LIST = ImmutableList.of(
             TextReviewServiceEnum.COMMENT_DETECTION_PRO.getService(),
@@ -1132,6 +1136,121 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
         return iPageR;
     }
 
+    @Override
+    public R<IPage<MyMergedReviewItemVo>> getMyMergedRatingList(Integer pageNum, Integer pageSize, Integer businessType,
+                                                                Long userId, Integer auditStatus, Integer currentUserId) {
+        if (userId == null) {
+            return R.fail("userId不能为空");
+        }
+        int pn = pageNum == null || pageNum < 1 ? 1 : pageNum;
+        int ps = pageSize == null || pageSize < 1 ? 10 : pageSize;
+        int perSource = Math.min(pn * ps, 1000);
+        int biz = businessType != null ? businessType : RatingBusinessTypeEnum.STORE_RATING.getBusinessType();
+
+        List<MyMergedReviewItemVo> merged = new ArrayList<>();
+
+        R storeR = getMyRatingList(1, perSource, biz, userId, auditStatus);
+        if (R.isSuccess(storeR) && storeR.getData() != null) {
+            IPage<?> sp = (IPage<?>) storeR.getData();
+            for (Object o : sp.getRecords()) {
+                if (o instanceof CommonRatingVo) {
+                    merged.add(toMergedStoreItem((CommonRatingVo) o));
+                }
+            }
+        }
+
+        R<IPage<OrderReviewVo>> lawyerR = orderReviewService.getMyReviewList(1, perSource, userId.intValue(), currentUserId);
+        if (R.isSuccess(lawyerR) && lawyerR.getData() != null && lawyerR.getData().getRecords() != null) {
+            for (OrderReviewVo vo : lawyerR.getData().getRecords()) {
+                merged.add(toMergedLawyerItem(vo));
+            }
+        }
+
+        merged.sort(Comparator.comparing(MyMergedReviewItemVo::getPublishTime,
+                Comparator.nullsLast(Comparator.naturalOrder())).reversed());
+
+        long storeTotal = 0L;
+        if (R.isSuccess(storeR) && storeR.getData() != null) {
+            storeTotal = ((IPage<?>) storeR.getData()).getTotal();
+        }
+        long lawyerTotal = 0L;
+        if (R.isSuccess(lawyerR) && lawyerR.getData() != null) {
+            lawyerTotal = lawyerR.getData().getTotal();
+        }
+        long total = storeTotal + lawyerTotal;
+
+        int from = (pn - 1) * ps;
+        List<MyMergedReviewItemVo> pageSlice;
+        if (from >= merged.size()) {
+            pageSlice = Collections.emptyList();
+        } else {
+            int to = Math.min(from + ps, merged.size());
+            pageSlice = merged.subList(from, to);
+        }
+
+        Page<MyMergedReviewItemVo> page = new Page<>(pn, ps, total);
+        page.setRecords(pageSlice);
+        return R.data(page);
+    }
+
+    private static MyMergedReviewItemVo toMergedStoreItem(CommonRatingVo vo) {
+        MyMergedReviewItemVo m = new MyMergedReviewItemVo();
+        m.setItemType("STORE");
+        m.setStoreRatingId(vo.getId());
+        m.setPublishTime(vo.getCreatedTime());
+        m.setScore(vo.getScore());
+        m.setContent(vo.getContent());
+        m.setMediaUrls(splitImageUrlsToMediaList(vo.getImageUrls()));
+        m.setStoreId(vo.getBusinessId());
+        m.setStoreName(vo.getStoreName());
+        m.setStoreIcon(null);
+        m.setBusinessType(vo.getBusinessType());
+        return m;
+    }
+
+    private static MyMergedReviewItemVo toMergedLawyerItem(OrderReviewVo vo) {
+        MyMergedReviewItemVo m = new MyMergedReviewItemVo();
+        m.setItemType("LAWYER");
+        m.setLawyerReviewId(vo.getId());
+        m.setPublishTime(vo.getCreatedTime());
+        m.setScore(vo.getOverallRating());
+        m.setContent(vo.getReviewContent());
+        m.setMediaUrls(vo.getReviewImages() != null ? new ArrayList<>(vo.getReviewImages()) : new ArrayList<>());
+        m.setLawyerName(vo.getLawyerName());
+        m.setLawyerAvatar(vo.getLawyerAvatar());
+        m.setLawyerFirm(vo.getLawFirmName());
+        m.setRatingTag(lawyerRatingTagText(vo.getOverallRating()));
+        m.setLawyerUserId(vo.getLawyerUserId());
+        m.setOrderId(vo.getOrderId());
+        return m;
+    }
+
+    private static List<String> splitImageUrlsToMediaList(String imageUrls) {
+        if (imageUrls == null || imageUrls.trim().isEmpty()) {
+            return new ArrayList<>();
+        }
+        return Arrays.stream(imageUrls.split(","))
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .collect(Collectors.toList());
+    }
+
+    private static String lawyerRatingTagText(Double overallRating) {
+        if (overallRating == null) {
+            return "我给出评价";
+        }
+        if (overallRating >= 4.5D) {
+            return "我给出超赞";
+        }
+        if (overallRating >= 3.5D) {
+            return "我给出好评";
+        }
+        if (overallRating >= 2.5D) {
+            return "我给出中评";
+        }
+        return "我给出差评";
+    }
+
 /*
     @Override
     public Double getAverageScore(Integer businessType, Long businessId) {

+ 186 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeUserPersonalizationSettingServiceImpl.java

@@ -0,0 +1,186 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeUserPersonalizationSetting;
+import shop.alien.mapper.LifeUserPersonalizationSettingMapper;
+import shop.alien.store.service.LifeUserPersonalizationSettingService;
+
+@Service
+@Slf4j
+public class LifeUserPersonalizationSettingServiceImpl
+        extends ServiceImpl<LifeUserPersonalizationSettingMapper, LifeUserPersonalizationSetting>
+        implements LifeUserPersonalizationSettingService {
+
+    @Override
+    public R<String> add(LifeUserPersonalizationSetting setting) {
+        log.info("LifeUserPersonalizationSettingServiceImpl.add, param={}", setting);
+        R<String> validated = validateAndApplyFontRule(setting, null);
+        if (validated != null) {
+            return validated;
+        }
+        boolean result = this.save(setting);
+        if (result) {
+            return R.success("新增成功");
+        }
+        return R.fail("新增失败");
+    }
+
+    @Override
+    public R<String> deleteById(Integer id) {
+        log.info("LifeUserPersonalizationSettingServiceImpl.deleteById, id={}", id);
+        boolean result = this.removeById(id);
+        if (result) {
+            return R.success("删除成功");
+        }
+        return R.fail("删除失败");
+    }
+
+    @Override
+    public R<String> update(LifeUserPersonalizationSetting setting) {
+        log.info("LifeUserPersonalizationSettingServiceImpl.update, param={}", setting);
+        LifeUserPersonalizationSetting existing;
+
+        if (setting.getId() != null) {
+            existing = this.getById(setting.getId());
+            if (existing == null) {
+                return R.fail("记录不存在");
+            }
+            if (setting.getUserId() != null && !setting.getUserId().equals(existing.getUserId())) {
+                return R.fail("userId与记录不一致");
+            }
+        } else if (setting.getUserId() != null) {
+            LambdaQueryWrapper<LifeUserPersonalizationSetting> w = new LambdaQueryWrapper<>();
+            w.eq(LifeUserPersonalizationSetting::getUserId, setting.getUserId());
+            existing = this.getOne(w);
+            if (existing == null) {
+                LifeUserPersonalizationSetting created = buildDefaultForUser(setting.getUserId());
+                R<String> validatedInit = validateAndApplyFontRule(created, null);
+                if (validatedInit != null) {
+                    return validatedInit;
+                }
+                try {
+                    this.save(created);
+                    existing = created;
+                } catch (DataIntegrityViolationException e) {
+                    log.warn("LifeUserPersonalizationSetting update 并发初始化 userId={}", setting.getUserId(), e);
+                    existing = this.getOne(w);
+                    if (existing == null) {
+                        return R.fail("记录不存在");
+                    }
+                }
+            }
+            setting.setId(existing.getId());
+        } else {
+            return R.fail("id与userId至少填写一个");
+        }
+
+        R<String> validated = validateAndApplyFontRule(setting, existing);
+        if (validated != null) {
+            return validated;
+        }
+        boolean result = this.updateById(setting);
+        if (result) {
+            return R.success("更新成功");
+        }
+        return R.fail("更新失败");
+    }
+
+    /**
+     * followSystemFont=1 跟随系统:不采纳请求中的 chatFontLevel,沿用库中已有值(新增时为 0)。
+     * followSystemFont=0:chatFontLevel 必须在 0~4(缺省按 0)。
+     */
+    private R<String> validateAndApplyFontRule(LifeUserPersonalizationSetting incoming, LifeUserPersonalizationSetting existing) {
+        int followSys = incoming.getFollowSystemFont() != null
+                ? incoming.getFollowSystemFont()
+                : (existing != null && existing.getFollowSystemFont() != null ? existing.getFollowSystemFont() : 1);
+        if (followSys != 0 && followSys != 1) {
+            return R.fail("followSystemFont 只能为 0 或 1");
+        }
+        if (followSys == 1) {
+            Integer keep = existing != null ? existing.getChatFontLevel() : null;
+            incoming.setChatFontLevel(keep != null ? keep : 0);
+            return null;
+        }
+        int level = incoming.getChatFontLevel() != null
+                ? incoming.getChatFontLevel()
+                : (existing != null && existing.getChatFontLevel() != null ? existing.getChatFontLevel() : 0);
+        if (level < 0 || level > 4) {
+            return R.fail("chatFontLevel 取值 0~4");
+        }
+        incoming.setChatFontLevel(level);
+        return null;
+    }
+
+    @Override
+    public R<LifeUserPersonalizationSetting> getInfoById(Integer id) {
+        log.info("LifeUserPersonalizationSettingServiceImpl.getInfoById, id={}", id);
+        return R.data(this.getById(id));
+    }
+
+    @Override
+    public R<LifeUserPersonalizationSetting> getByUserId(Integer userId) {
+        log.info("LifeUserPersonalizationSettingServiceImpl.getByUserId, userId={}", userId);
+        if (userId == null) {
+            return R.fail("userId不能为空");
+        }
+        LambdaQueryWrapper<LifeUserPersonalizationSetting> w = new LambdaQueryWrapper<>();
+        w.eq(LifeUserPersonalizationSetting::getUserId, userId);
+        LifeUserPersonalizationSetting one = this.getOne(w);
+        if (one != null) {
+            return R.data(one);
+        }
+        LifeUserPersonalizationSetting created = buildDefaultForUser(userId);
+        R<String> validated = validateAndApplyFontRule(created, null);
+        if (validated != null) {
+            return R.fail(validated.getMsg());
+        }
+        try {
+            this.save(created);
+            return R.data(created);
+        } catch (DataIntegrityViolationException e) {
+            log.warn("LifeUserPersonalizationSetting getByUserId 并发插入 userId={}", userId, e);
+            LifeUserPersonalizationSetting again = this.getOne(w);
+            if (again != null) {
+                return R.data(again);
+            }
+            return R.fail("初始化个性化设置失败");
+        }
+    }
+
+    /** 与表默认值一致,便于无记录时落库 */
+    private static LifeUserPersonalizationSetting buildDefaultForUser(Integer userId) {
+        LifeUserPersonalizationSetting s = new LifeUserPersonalizationSetting();
+        s.setUserId(userId);
+        s.setPersonalizedRecommendation(1);
+        s.setOnlyFollowersComment(1);
+        s.setHideFansList(1);
+        s.setHideFollowList(1);
+        s.setNotifyReceiveMessage(1);
+        s.setNotifyLike(1);
+        s.setNotifyFollow(1);
+        s.setNotifyComment(1);
+        s.setFollowSystemFont(1);
+        s.setChatFontLevel(0);
+        s.setAutoRefresh(1);
+        return s;
+    }
+
+    @Override
+    public R<IPage<LifeUserPersonalizationSetting>> list(Integer pageNum, Integer pageSize, Integer userId) {
+        log.info("LifeUserPersonalizationSettingServiceImpl.list, pageNum={}, pageSize={}, userId={}", pageNum, pageSize, userId);
+        Page<LifeUserPersonalizationSetting> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<LifeUserPersonalizationSetting> w = new LambdaQueryWrapper<>();
+        if (userId != null) {
+            w.eq(LifeUserPersonalizationSetting::getUserId, userId);
+        }
+        w.orderByDesc(LifeUserPersonalizationSetting::getUpdatedTime);
+        return R.data(this.page(page, w));
+    }
+}