Преглед изворни кода

Merge remote-tracking branch 'origin/store-plantform' into store-plantform

wxd пре 3 недеља
родитељ
комит
061586dac1
53 измењених фајлова са 5739 додато и 128 уклоњено
  1. 23 0
      alien-config/src/main/java/shop/alien/config/redis/BaseRedisService.java
  2. 79 0
      alien-entity/src/main/java/shop/alien/entity/second/SecondEntrustUser.java
  3. 16 0
      alien-entity/src/main/java/shop/alien/entity/second/SecondTradeRecord.java
  4. 39 0
      alien-entity/src/main/java/shop/alien/entity/second/vo/SecondEntrustUserDTO.java
  5. 30 0
      alien-entity/src/main/java/shop/alien/entity/second/vo/SecondEntrustUserDetailVo.java
  6. 41 0
      alien-entity/src/main/java/shop/alien/entity/second/vo/SecondEntrustUserQueryVo.java
  7. 39 0
      alien-entity/src/main/java/shop/alien/entity/second/vo/SecondEntrustUserResultVo.java
  8. 15 0
      alien-entity/src/main/java/shop/alien/entity/second/vo/SecondTradeRecordVo.java
  9. 117 0
      alien-entity/src/main/java/shop/alien/entity/second/vo/SellerEvaluationVo.java
  10. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/LifeMessage.java
  11. 68 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreLicenseHistory.java
  12. 57 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreLicenseHistoryDTO.java
  13. 63 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreLicenseHistoryVO.java
  14. 5 1
      alien-entity/src/main/java/shop/alien/entity/store/vo/WebSocketVo.java
  15. 17 0
      alien-entity/src/main/java/shop/alien/mapper/StoreLicenseHistoryMapper.java
  16. 67 0
      alien-entity/src/main/java/shop/alien/mapper/second/SecondEntrustUserMapper.java
  17. 2 2
      alien-entity/src/main/java/shop/alien/mapper/second/SecondRecommendMapper.java
  18. 0 3
      alien-entity/src/main/java/shop/alien/mapper/second/SecondTradeRecordMapper.java
  19. 29 0
      alien-entity/src/main/resources/mapper/second/SecondEntrustUserMapper.xml
  20. 88 84
      alien-entity/src/main/resources/mapper/second/SecondGoodsInfoMapper.xml
  21. 580 0
      alien-second/doc/委托人信息分页查询接口文档.md
  22. 631 0
      alien-second/doc/委托人详情查询接口文档.md
  23. 582 0
      alien-second/doc/获取卖家交易评价列表接口文档.md
  24. 471 0
      alien-second/src/main/java/shop/alien/second/config/GaoDeMapUtil.java
  25. 206 0
      alien-second/src/main/java/shop/alien/second/controller/SecondEntrustUserController.java
  26. 33 0
      alien-second/src/main/java/shop/alien/second/controller/SecondGaoDeController.java
  27. 6 4
      alien-second/src/main/java/shop/alien/second/controller/SecondRecommendController.java
  28. 68 9
      alien-second/src/main/java/shop/alien/second/controller/SecondTradeRecordController.java
  29. 90 0
      alien-second/src/main/java/shop/alien/second/service/SecondEntrustUserService.java
  30. 2 2
      alien-second/src/main/java/shop/alien/second/service/SecondRecommendService.java
  31. 23 3
      alien-second/src/main/java/shop/alien/second/service/SecondTradeRecordService.java
  32. 296 0
      alien-second/src/main/java/shop/alien/second/service/impl/SecondEntrustUserServiceImpl.java
  33. 4 4
      alien-second/src/main/java/shop/alien/second/service/impl/SecondRecommendServiceImpl.java
  34. 446 14
      alien-second/src/main/java/shop/alien/second/service/impl/SecondTradeRecordServiceImpl.java
  35. 171 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StoreLicenseHistoryController.java
  36. 42 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformInfoController.java
  37. 12 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/NearMeService.java
  38. 79 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StoreLicenseHistoryService.java
  39. 16 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformInfoService.java
  40. 27 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/NearMeServiceImpl.java
  41. 241 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreLicenseHistoryServiceImpl.java
  42. 196 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformInfoServiceImpl.java
  43. 22 0
      alien-store/src/main/java/shop/alien/store/controller/AliController.java
  44. 5 1
      alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponStoreFriendServiceImpl.java
  45. 75 0
      alien-store/src/main/java/shop/alien/store/util/ali/AliApi.java
  46. 95 0
      alien-store/src/main/java/shop/alien/store/util/ali/ocr/AbstractOcrStrategy.java
  47. 49 0
      alien-store/src/main/java/shop/alien/store/util/ali/ocr/OcrStrategy.java
  48. 79 0
      alien-store/src/main/java/shop/alien/store/util/ali/ocr/OcrStrategyFactory.java
  49. 115 0
      alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/BusinessLicenseOcrStrategy.java
  50. 111 0
      alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/FoodManageLicenseOcrStrategy.java
  51. 113 0
      alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/IdCardOcrStrategy.java
  52. 7 0
      alien-util/pom.xml
  53. 50 0
      alien-util/src/main/java/shop/alien/util/common/constant/OcrTypeEnum.java

+ 23 - 0
alien-config/src/main/java/shop/alien/config/redis/BaseRedisService.java

@@ -1,6 +1,7 @@
 package shop.alien.config.redis;
 
 import lombok.RequiredArgsConstructor;
+import org.springframework.data.geo.Point;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.data.redis.core.script.RedisScript;
 import org.springframework.stereotype.Component;
@@ -113,6 +114,16 @@ public class BaseRedisService {
     public void setString(String key, String value, Long timeOut) {
         set(key, value, timeOut);
     }
+
+    /**
+     * 判断是否存在指定key
+     * @param key
+     * @return
+     */
+    public boolean hasKey(String key) {
+        Boolean exists = stringRedisTemplate.hasKey(key);
+        return exists != null && exists;
+    }
     
     /**
      * 添加String值, 如果key不存在则设置成功并返回true,否则返回false
@@ -174,4 +185,16 @@ public class BaseRedisService {
     public void delete(String key) {
         stringRedisTemplate.delete(key);
     }
+
+    /**
+     * 添加地理信息
+     *
+     * @param point
+     * @param content
+     * @param type
+     * @return
+     */
+    public Long inGeolocation(Point point, String content, String type) {
+        return stringRedisTemplate.opsForGeo().add(type, point, content);
+    }
 }

+ 79 - 0
alien-entity/src/main/java/shop/alien/entity/second/SecondEntrustUser.java

@@ -0,0 +1,79 @@
+package shop.alien.entity.second;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+/**
+ * <p>
+ * 二手委托人信息表
+ * </p>
+ *
+ * @author ssk
+ * @since 2025-11-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName("second_entrust_user")
+@ApiModel(value = "SecondEntrustUser对象", description = "二手委托人信息表")
+public class SecondEntrustUser extends Model<SecondEntrustUser> {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "交易ID")
+    @TableField("entrust_trade_id")
+    private Integer entrustTradeId;
+
+    @ApiModelProperty(value = "交易编号")
+    @TableField("entrust_trade_no")
+    private String entrustTradeNo;
+
+    @ApiModelProperty(value = "委托人电话")
+    @TableField("entrust_user_phone")
+    private String entrustUserPhone;
+
+    @ApiModelProperty(value = "委托人姓名")
+    @TableField("entrust_user_name")
+    private String entrustUserName;
+
+    @ApiModelProperty(value = "委托人身份证")
+    @TableField("entrust_id_card")
+    private String entrustIdCard;
+
+    @ApiModelProperty(value = "委托人身份证照片")
+    @TableField("entrust_id_card_img")
+    private String entrustIdCardImg;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField(value = "created_user_id", fill = FieldFill.INSERT)
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField(value = "updated_user_id", fill = FieldFill.INSERT_UPDATE)
+    private Integer updatedUserId;
+
+}
+

+ 16 - 0
alien-entity/src/main/java/shop/alien/entity/second/SecondTradeRecord.java

@@ -107,18 +107,34 @@ public class SecondTradeRecord extends Model<SecondTradeRecord> {
     @TableField("buyer_transaction_status")
     private Integer buyerTransactionStatus;
 
+    @ApiModelProperty(value = "买家完成交易时间")
+    @TableField("buyer_complete_time")
+    private Date buyerCompleteTime;
+
     @ApiModelProperty(value = "买家评价")
     @TableField("buyer_evaluate")
     private String buyerEvaluate;
 
+    @ApiModelProperty(value = "买家评分")
+    @TableField("buyer_rating")
+    private Integer buyerRating;
+
     @ApiModelProperty(value = "卖家交易状态  0-未确认  1-交易成功  2-交易失败")
     @TableField("seller_transaction_status")
     private Integer sellerTransactionStatus;
 
+    @ApiModelProperty(value = "卖家完成交易时间")
+    @TableField("seller_complete_time")
+    private Date sellerCompleteTime;
+
     @ApiModelProperty(value = "卖家评价")
     @TableField("seller_evaluate")
     private String sellerEvaluate;
 
+    @ApiModelProperty(value = "卖家评分")
+    @TableField("seller_rating")
+    private Integer sellerRating;
+
     @ApiModelProperty(value = "交易状态  1-待确认  2-已拒绝  3-待交易  4-交易成功  5-交易失败  6-交易取消")
     @TableField("trade_status")
     private Integer tradeStatus;

+ 39 - 0
alien-entity/src/main/java/shop/alien/entity/second/vo/SecondEntrustUserDTO.java

@@ -0,0 +1,39 @@
+package shop.alien.entity.second.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.second.SecondTradeRecord;
+
+/**
+ * <p>
+ * 二手委托人信息 DTO
+ * </p>
+ *
+ * @author ssk
+ * @since 2025-11-21
+ */
+@Data
+@ApiModel(value = "SecondEntrustUserDTO", description = "二手委托人信息传输对象")
+public class SecondEntrustUserDTO extends SecondTradeRecord {
+
+    @ApiModelProperty(value = "交易ID")
+    private Integer entrustTradeId;
+
+    @ApiModelProperty(value = "交易编号")
+    private String entrustTradeNo;
+
+    @ApiModelProperty(value = "委托人电话")
+    private String entrustUserPhone;
+
+    @ApiModelProperty(value = "委托人姓名")
+    private String entrustUserName;
+
+    @ApiModelProperty(value = "委托人身份证")
+    private String entrustIdCard;
+
+    @ApiModelProperty(value = "委托人身份证照片")
+    private String entrustIdCardImg;
+
+}
+

+ 30 - 0
alien-entity/src/main/java/shop/alien/entity/second/vo/SecondEntrustUserDetailVo.java

@@ -0,0 +1,30 @@
+package shop.alien.entity.second.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.second.SecondEntrustUser;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 二手委托人详情 VO
+ * </p>
+ *
+ * @author ssk
+ * @since 2025-11-21
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "SecondEntrustUserDetailVo", description = "二手委托人详情对象")
+public class SecondEntrustUserDetailVo {
+
+    @ApiModelProperty(value = "委托人基本信息")
+    private SecondEntrustUser entrustUserInfo;
+
+    @ApiModelProperty(value = "关联的交易记录集合")
+    private List<SecondTradeRecordVo> tradeRecords;
+}
+

+ 41 - 0
alien-entity/src/main/java/shop/alien/entity/second/vo/SecondEntrustUserQueryVo.java

@@ -0,0 +1,41 @@
+package shop.alien.entity.second.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * <p>
+ * 二手委托人信息查询 VO(以人为维度)
+ * </p>
+ *
+ * @author ssk
+ * @since 2025-11-21
+ */
+@Data
+@JsonInclude
+@NoArgsConstructor
+@ApiModel(value = "SecondEntrustUserQueryVo", description = "二手委托人信息查询对象(以人为维度)")
+public class SecondEntrustUserQueryVo {
+
+    @ApiModelProperty(value = "分页页数")
+    private Integer pageNum;
+
+    @ApiModelProperty(value = "分页条数")
+    private Integer pageSize;
+
+    @ApiModelProperty(value = "委托人姓名")
+    private String entrustUserName;
+
+    @ApiModelProperty(value = "委托人身份证号")
+    private String entrustIdCard;
+
+    @ApiModelProperty(value = "委托人电话")
+    private String entrustUserPhone;
+
+    @ApiModelProperty(value = "交易编号")
+    private String entrustTradeNo;
+}
+

+ 39 - 0
alien-entity/src/main/java/shop/alien/entity/second/vo/SecondEntrustUserResultVo.java

@@ -0,0 +1,39 @@
+package shop.alien.entity.second.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import shop.alien.entity.second.SecondEntrustUser;
+
+/**
+ * <p>
+ * 二手委托人信息结果 VO(以人为维度)
+ * </p>
+ *
+ * @author ssk
+ * @since 2025-11-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@JsonInclude
+@NoArgsConstructor
+@ApiModel(value = "SecondEntrustUserResultVo", description = "二手委托人信息结果对象(以人为维度)")
+public class SecondEntrustUserResultVo extends SecondEntrustUser {
+
+    @TableField(exist = false)
+    @ApiModelProperty(value = "委托记录总数")
+    private Integer entrustCount;
+
+    @TableField(exist = false)
+    @ApiModelProperty(value = "关联交易总数")
+    private Integer tradeCount;
+
+    @TableField(exist = false)
+    @ApiModelProperty(value = "最近委托时间")
+    private String latestEntrustTime;
+}
+

+ 15 - 0
alien-entity/src/main/java/shop/alien/entity/second/vo/SecondTradeRecordVo.java

@@ -62,6 +62,9 @@ public class SecondTradeRecordVo extends SecondTradeRecord {
     @ApiModelProperty(value = "用户交易评价")
     private String userEvaluate;
 
+    @ApiModelProperty(value = "用户交易评分")
+    private Integer userRating;
+
     @ApiModelProperty(value = "当前时间是否超过交易时间  0-否  1-是")
     private String timeOutFlag;
 
@@ -73,4 +76,16 @@ public class SecondTradeRecordVo extends SecondTradeRecord {
 
     @ApiModelProperty(value = "商品信息")
     private SecondGoodsVo goodsInfo;
+
+    @ApiModelProperty(value = "委托人ID")
+    private Integer entrustId;
+
+    @ApiModelProperty(value = "委托人电话")
+    private String entrustUserPhone;
+
+    @ApiModelProperty(value = "委托人姓名")
+    private String entrustUserName;
+
+    @ApiModelProperty(value = "委托人身份证")
+    private String entrustIdCard;
 }

+ 117 - 0
alien-entity/src/main/java/shop/alien/entity/second/vo/SellerEvaluationVo.java

@@ -0,0 +1,117 @@
+package shop.alien.entity.second.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+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.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * <p>
+ * 卖家交易评价VO
+ * </p>
+ *
+ * @author system
+ * @since 2025-11-18
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "SellerEvaluationVo对象", description = "卖家交易评价VO")
+public class SellerEvaluationVo {
+
+    @ApiModelProperty(value = "交易记录ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "交易编号")
+    private String tradeNo;
+
+    @ApiModelProperty(value = "商品ID")
+    private Integer goodsId;
+
+    @ApiModelProperty(value = "商品标题")
+    private String goodsTitle;
+
+    @ApiModelProperty(value = "商品封面图片")
+    private String goodsHomeImage;
+
+    @ApiModelProperty(value = "商品价格")
+    private BigDecimal goodsPrice;
+
+    @ApiModelProperty(value = "交易金额")
+    private BigDecimal transactionAmount;
+
+    @ApiModelProperty(value = "买家ID")
+    private Integer buyerId;
+
+    @ApiModelProperty(value = "买家昵称")
+    private String buyerName;
+
+    @ApiModelProperty(value = "买家头像")
+    private String buyerImage;
+
+    @ApiModelProperty(value = "买家手机号")
+    private String buyerPhone;
+
+    @ApiModelProperty(value = "买家真实姓名")
+    private String buyerRealName;
+
+    @ApiModelProperty(value = "买家性别")
+    private String buyerSex;
+
+    @ApiModelProperty(value = "买家简介")
+    private String buyerJianjie;
+
+    @ApiModelProperty(value = "买家评价内容")
+    private String buyerEvaluate;
+
+    @ApiModelProperty(value = "买家评分(1-5分)")
+    private Integer buyerRating;
+
+    @ApiModelProperty(value = "买家信用积分")
+    private Integer buyerCreditScore;
+
+    @ApiModelProperty(value = "买家风控评分等级(A-E,O表示无记录)")
+    private String buyerCreditLevel;
+
+    @ApiModelProperty(value = "卖家ID")
+    private Integer sellerId;
+
+    @ApiModelProperty(value = "卖家昵称")
+    private String sellerName;
+
+    @ApiModelProperty(value = "卖家头像")
+    private String sellerImage;
+
+    @ApiModelProperty(value = "卖家信用积分")
+    private Integer sellerCreditScore;
+
+    @ApiModelProperty(value = "卖家风控评分等级(A-E,O表示无记录)")
+    private String sellerCreditLevel;
+
+    @ApiModelProperty(value = "交易时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date transactionTime;
+
+    @ApiModelProperty(value = "交易状态  1-待确认  2-已拒绝  3-待交易  4-交易成功  5-交易失败  6-交易取消")
+    private Integer tradeStatus;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+
+    @ApiModelProperty(value = "买家完成交易时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date buyerCompleteTime;
+
+    @ApiModelProperty(value = "卖家完成交易时间")
+    @TableField("seller_complete_time")
+    private Date sellerCompleteTime;
+
+
+}
+

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/store/LifeMessage.java

@@ -34,7 +34,7 @@ public class LifeMessage {
     @ApiModelProperty(value = "发送内容")
     private String content;
 
-    @ApiModelProperty(value = "消息类型  1-文本  2-图片 3-链接  4-二手交易创建/确认/拒绝/取消  5-二手交易签到提醒  6-二手交易已签到  7-消息内容不合规  8-律师消息  9-二手交易修改交易请求  10-二手交易提醒进入实时定位")
+    @ApiModelProperty(value = "消息类型  1-文本  2-图片 3-链接  4-二手交易创建/确认/拒绝/取消  5-二手交易签到提醒  6-二手交易已签到  7-消息内容不合规  8-律师消息  9-二手交易修改交易请求  10-二手交易提醒进入实时定位  11-二手交易委托  12-二手交易是否与交易有关的言论温馨提示")
     private String type;
 
     private Date currentTime;

+ 68 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreLicenseHistory.java

@@ -0,0 +1,68 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+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.util.Date;
+
+/**
+ * 商户证照历史记录
+ *
+ * @author system
+ * @since 2025-11-24
+ */
+@Data
+@JsonInclude
+@TableName("store_license_history")
+@ApiModel(value = "StoreLicenseHistory对象", description = "商户证照历史记录")
+public class StoreLicenseHistory extends Model<StoreLicenseHistory> {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "商户ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "证照类型: 1-合同管理, 2-食品经营许可证")
+    @TableField("license_status")
+    private Integer licenseStatus;
+
+    @ApiModelProperty(value = "审核状态: 1-审核中, 2-审核拒绝, 3-审核通过")
+    @TableField("license_execute_status")
+    private Integer licenseExecuteStatus;
+
+    @ApiModelProperty(value = "图片URL")
+    @TableField("img_url")
+    private String imgUrl;
+
+    @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;
+}
+

+ 57 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreLicenseHistoryDTO.java

@@ -0,0 +1,57 @@
+package shop.alien.entity.store.dto;
+
+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 org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * 商户证照历史记录DTO
+ *
+ * @author system
+ * @since 2025-11-24
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreLicenseHistoryDTO对象", description = "商户证照历史记录DTO")
+public class StoreLicenseHistoryDTO {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "商户ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "证照类型: 1-合同管理, 2-食品经营许可证", required = true)
+    private Integer licenseStatus;
+
+    @ApiModelProperty(value = "审核状态: 1-审核中, 2-审核拒绝, 3-审核通过", required = true)
+    private Integer licenseExecuteStatus;
+
+    @ApiModelProperty(value = "图片URL", required = true)
+    private String imgUrl;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    private Integer updatedUserId;
+}
+

+ 63 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreLicenseHistoryVO.java

@@ -0,0 +1,63 @@
+package shop.alien.entity.store.vo;
+
+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.util.Date;
+
+/**
+ * 商户证照历史记录VO
+ *
+ * @author system
+ * @since 2025-11-24
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreLicenseHistoryVO对象", description = "商户证照历史记录VO")
+public class StoreLicenseHistoryVO {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "商户ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "商户名称")
+    private String storeName;
+
+    @ApiModelProperty(value = "证照类型: 1-合同管理, 2-食品经营许可证")
+    private Integer licenseStatus;
+
+    @ApiModelProperty(value = "证照类型名称")
+    private String licenseStatusName;
+
+    @ApiModelProperty(value = "审核状态: 1-审核中, 2-审核拒绝, 3-审核通过")
+    private Integer licenseExecuteStatus;
+
+    @ApiModelProperty(value = "审核状态名称")
+    private String licenseExecuteStatusName;
+
+    @ApiModelProperty(value = "图片URL")
+    private String imgUrl;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    private Integer updatedUserId;
+}
+

+ 5 - 1
alien-entity/src/main/java/shop/alien/entity/store/vo/WebSocketVo.java

@@ -6,6 +6,7 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.util.Date;
+import java.util.List;
 
 @Data
 @JsonInclude
@@ -26,7 +27,7 @@ public class WebSocketVo {
     @ApiModelProperty(value = "是否已读  0:未读  1:已读")
     private Integer isRead;
 
-    @ApiModelProperty(value = "消息类型  1-文本  2-图片 3-链接分享  4-二手交易(确认/拒绝/取消)  5-二手交易签到提醒  6-二手交易已签到  7-消息内容不合规  8-律师消息  9-二手交易修改交易请求  10-二手交易提醒进入实时定位")
+    @ApiModelProperty(value = "消息类型  1-文本  2-图片 3-链接分享  4-二手交易(确认/拒绝/取消)  5-二手交易签到提醒  6-二手交易已签到  7-消息内容不合规  8-律师消息  9-二手交易修改交易请求  10-二手交易提醒进入实时定位  11-二手交易委托  12-二手交易是否与交易有关的言论温馨提示")
     private String type;
 
     @ApiModelProperty(value = "消息内容")
@@ -35,6 +36,9 @@ public class WebSocketVo {
     @ApiModelProperty(value = "消息id")
     private Integer messageId;
 
+    @ApiModelProperty(value = "消息idList")
+    private List<Integer> messageIdList;
+
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @ApiModelProperty(value = "创建时间")
     private Date createdTime;

+ 17 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreLicenseHistoryMapper.java

@@ -0,0 +1,17 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StoreLicenseHistory;
+
+/**
+ * 商户证照历史记录 Mapper 接口
+ *
+ * @author system
+ * @since 2025-11-24
+ */
+@Mapper
+public interface StoreLicenseHistoryMapper extends BaseMapper<StoreLicenseHistory> {
+
+}
+

+ 67 - 0
alien-entity/src/main/java/shop/alien/mapper/second/SecondEntrustUserMapper.java

@@ -0,0 +1,67 @@
+package shop.alien.mapper.second;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import shop.alien.entity.second.SecondEntrustUser;
+import shop.alien.entity.second.vo.SecondEntrustUserResultVo;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 二手委托人信息表 Mapper 接口
+ * </p>
+ *
+ * @author ssk
+ * @since 2025-11-21
+ */
+@Mapper
+public interface SecondEntrustUserMapper extends BaseMapper<SecondEntrustUser> {
+
+    /**
+     * 分页查询委托人信息列表(以姓名+身份证号为维度分组)
+     *
+     * @param page 分页对象
+     * @param wrapper 查询条件
+     * @return 委托人信息分页列表
+     */
+    @Select("select " +
+            "  MIN(entrust.id) as id, " +
+            "  entrust.entrust_user_name as entrustUserName, " +
+            "  entrust.entrust_id_card as entrustIdCard, " +
+            "  entrust.entrust_user_phone as entrustUserPhone, " +
+            "  entrust.entrust_id_card_img as entrustIdCardImg, " +
+            "  COUNT(DISTINCT entrust.id) as entrustCount, " +
+            "  COUNT(DISTINCT entrust.entrust_trade_id) as tradeCount, " +
+            "  MAX(entrust.created_time) as latestEntrustTime " +
+            "from second_entrust_user entrust " +
+            "left join second_trade_record trade on trade.id = entrust.entrust_trade_id " +
+            "${ew.customSqlSegment} " +
+            "group by entrust.entrust_user_name, entrust.entrust_id_card " +
+            "order by latestEntrustTime desc")
+    IPage<SecondEntrustUserResultVo> getEntrustUserPage(IPage<SecondEntrustUser> page, 
+                                                         @Param(Constants.WRAPPER) QueryWrapper<SecondEntrustUser> wrapper);
+
+    /**
+     * 根据姓名和身份证号查询所有委托记录
+     *
+     * @param entrustUserName 姓名
+     * @param entrustIdCard 身份证号
+     * @return 委托记录列表
+     */
+    @Select("select * from second_entrust_user " +
+            "where entrust_user_name = #{entrustUserName} " +
+            "and entrust_id_card = #{entrustIdCard} " +
+            "and delete_flag = 0 " +
+            "order by created_time desc")
+    List<SecondEntrustUser> getByUserNameAndIdCard(@Param("entrustUserName") String entrustUserName, 
+                                                     @Param("entrustIdCard") String entrustIdCard);
+
+}
+
+

+ 2 - 2
alien-entity/src/main/java/shop/alien/mapper/second/SecondRecommendMapper.java

@@ -17,11 +17,11 @@ public interface SecondRecommendMapper extends BaseMapper<SecondGoodsRecommendVo
     /**
      * 自定义分页查询
      */
-    IPage<SecondGoodsRecommendVo> getSecondRecommendByPage(IPage<SecondGoodsRecommendVo> page, @Param("userId") Integer userId, @Param("position") String position, @Param("typeId") Integer typeId, @Param("phoneId") String phoneId);
+    IPage<SecondGoodsRecommendVo> getSecondRecommendByPage(IPage<SecondGoodsRecommendVo> page, @Param("userId") Integer userId, @Param("position") String position, @Param("typeId") Integer typeId, @Param("phoneId") String phoneId, @Param("radiusKm") String radiusKm);
 
     IPage<SecondGoodsRecommendVo> querySecondConcernByPage(IPage<SecondGoodsRecommendVo> page, @Param("userId") Integer userId, @Param("phoneId") String phoneId, @Param("position") String position);
 
-    IPage<SecondGoodsRecommendVo> querySecondNewGoodsByPage(IPage<SecondGoodsRecommendVo> page,@Param("userId") String userId, @Param("phoneId") String phoneId, @Param("position") String position);
+    IPage<SecondGoodsRecommendVo> querySecondNewGoodsByPage(IPage<SecondGoodsRecommendVo> page,@Param("userId") String userId, @Param("phoneId") String phoneId, @Param("position") String position, @Param("radiusKm") String radiusKm);
 
     List<SecondCommentVo> querySecondCommentInfo(@Param("ids") List<Integer> ids);
 

+ 0 - 3
alien-entity/src/main/java/shop/alien/mapper/second/SecondTradeRecordMapper.java

@@ -6,15 +6,12 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.Constants;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import shop.alien.entity.second.SecondTradeRecord;
 import shop.alien.entity.second.vo.SecondTradeRecordVo;
-import shop.alien.entity.store.vo.LifeFansVo;
 
-import java.util.Date;
 import java.util.List;
 
 /**

+ 29 - 0
alien-entity/src/main/resources/mapper/second/SecondEntrustUserMapper.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.second.SecondEntrustUserMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.second.SecondEntrustUser">
+        <id column="id" property="id" />
+        <result column="entrust_trade_id" property="entrustTradeId" />
+        <result column="entrust_trade_no" property="entrustTradeNo" />
+        <result column="entrust_user_phone" property="entrustUserPhone" />
+        <result column="entrust_user_name" property="entrustUserName" />
+        <result column="entrust_id_card" property="entrustIdCard" />
+        <result column="entrust_id_card_img" property="entrustIdCardImg" />
+        <result column="delete_flag" property="deleteFlag" />
+        <result column="created_time" property="createdTime" />
+        <result column="created_user_id" property="createdUserId" />
+        <result column="updated_time" property="updatedTime" />
+        <result column="updated_user_id" property="updatedUserId" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, entrust_trade_id, entrust_trade_no, entrust_user_phone, entrust_user_name, 
+        entrust_id_card, entrust_id_card_img, delete_flag, created_time, created_user_id, 
+        updated_time, updated_user_id
+    </sql>
+
+</mapper>
+

+ 88 - 84
alien-entity/src/main/resources/mapper/second/SecondGoodsInfoMapper.xml

@@ -42,47 +42,49 @@
             where lb.delete_flag = 0 and lb.blocker_type = 2 and lb.blocked_type = 2
                 and lb.blocker_id = #{userId}
         )
-        SELECT
-            g.id,
-            g.user_id,
-            g.title,
-            g.description,
-            g.price,
-            g.price as amount,
-            g.position,
-            g.like_count,
-            g.collect_count,
-            g.category_one_id,
-            g.category_two_id,
-            g.label,
-            g.topic,
-            g.trade_id,
-            g.created_time,
-            g.created_user_id,
-            g.updated_time,
-            g.updated_user_id,
-            u.user_name,
-            u.user_image,
-            g.goods_status,
-            g.delete_flag,
-            g.release_time,
-            g.video_first_frame,
-            ROUND(ST_Distance_Sphere(ST_GeomFromText(CONCAT('POINT(', REPLACE(#{position}, ',', ' '), ')' )), ST_GeomFromText(CONCAT('POINT(', REPLACE(g.position, ',', ' '), ')' ))) / 1000, 2) AS dist,
-            case when llr.id is null then '0' else '1' end likeStatus,
-            g.home_image,
-            CONCAT('user_', u.user_phone) as user_phone
-        FROM
-            second_goods g inner join life_user u on u.id =  g.user_id and u.delete_flag = 0
-            left join life_like_record llr on llr.dianzan_id = #{phoneId} and llr.huifu_id = g.id and llr.type = 6 and llr.delete_flag = 0
-        where g.delete_flag = 0
-            <if test="typeId != null and typeId != '' ">
-                and (g.category_one_id = #{typeId} or g.category_two_id =#{typeId} )
-            </if>
-            and not exists (select 1 from shieldUser s where s.id = g.id)
-            and not exists (select 1 from second_shield s where s.user_id = #{userId} and s.shield_type = 1 and s.shield_id = g.id and s.delete_flag = 0)
-            and g.goods_status = 3
-            and g.position != '' and g.position is not null
-        order by dist, like_count desc, release_time desc
+        SELECT * FROM (
+            SELECT
+                g.id,
+                g.user_id,
+                g.title,
+                g.description,
+                g.price,
+                g.price as amount,
+                g.position,
+                g.like_count,
+                g.collect_count,
+                g.category_one_id,
+                g.category_two_id,
+                g.label,
+                g.topic,
+                g.trade_id,
+                g.created_time,
+                g.created_user_id,
+                g.updated_time,
+                g.updated_user_id,
+                u.user_name,
+                u.user_image,
+                g.goods_status,
+                g.delete_flag,
+                g.release_time,
+                g.video_first_frame,
+                ROUND(ST_Distance_Sphere(ST_GeomFromText(CONCAT('POINT(', REPLACE(#{position}, ',', ' '), ')' )), ST_GeomFromText(CONCAT('POINT(', REPLACE(g.position, ',', ' '), ')' ))) / 1000, 2) AS dist,
+                case when llr.id is null then '0' else '1' end likeStatus,
+                g.home_image,
+                CONCAT('user_', u.user_phone) as user_phone
+            FROM
+                second_goods g inner join life_user u on u.id =  g.user_id and u.delete_flag = 0
+                left join life_like_record llr on llr.dianzan_id = #{phoneId} and llr.huifu_id = g.id and llr.type = 6 and llr.delete_flag = 0
+            where g.delete_flag = 0
+                <if test="typeId != null and typeId != '' ">
+                    and (g.category_one_id = #{typeId} or g.category_two_id =#{typeId} )
+                </if>
+                and not exists (select 1 from shieldUser s where s.id = g.id)
+                and not exists (select 1 from second_shield s where s.user_id = #{userId} and s.shield_type = 1 and s.shield_id = g.id and s.delete_flag = 0)
+                and g.goods_status = 3
+                and g.position != '' and g.position is not null
+            order by dist, like_count desc, release_time desc
+        ) a where a.dist &lt; #{radiusKm}
     </select>
 
     <!-- 分页查询关注数据concern -->
@@ -173,49 +175,51 @@
             where lb.delete_flag = 0 and lb.blocker_type = 2 and lb.blocked_type = 2
                 and lb.blocker_id = #{userId}
         )
-        SELECT
-            g.id,
-            g.user_id,
-            g.title,
-            g.description,
-            g.price,
-            g.price as amount,
-            g.position,
-            g.like_count,
-            g.collect_count,
-            g.category_one_id,
-            g.category_two_id,
-            g.label,
-            g.topic,
-            g.trade_id,
-            g.created_time,
-            g.created_user_id,
-            g.updated_time,
-            g.updated_user_id,
-            g.release_time,
-            u.user_name,
-            u.user_image,
-            CONCAT('user_', u.user_phone) as user_phone,
-            g.goods_status,
-            g.delete_flag,
-            ROUND(ST_Distance_Sphere(ST_GeomFromText(CONCAT('POINT(', REPLACE(#{position}, ',', ' '), ')' )), ST_GeomFromText(CONCAT('POINT(', REPLACE(g.position, ',', ' '), ')' ))) / 1000, 2) AS dist,
-            g.home_image,
-            g.video_first_frame,
-            case when llr.id is null then '0' else '1' end likeStatus,
-            case when lc.id is null then '0' else '1' end collectStatus,
-            (select count(1) from (SELECT id FROM store_comment c where c.business_id = g.id and c.business_type = 7 and c.delete_flag = 0 UNION ALL
-            SELECT t.id FROM store_comment t INNER JOIN (SELECT id FROM store_comment c where c.business_id = g.id and c.business_type = 7 and c.delete_flag = 0) d ON t.reply_id = d.id and business_type = 6 and delete_flag = 0) a ) as commentCount
-<!--            (SELECT count(1) FROM store_comment c where c.business_id = g.id and c.business_type = 7 and c.delete_flag = 0 ) as commentCount-->
-        FROM
-            second_goods g inner join life_user u on u.id = g.user_id
-            left join life_like_record llr on llr.dianzan_id = #{phoneId} and llr.huifu_id = g.id and llr.type = 6 and llr.delete_flag = 0
-            left join life_collect lc on lc.business_id = g.id and lc.user_id = #{phoneId} and lc.delete_flag = 0 and lc.business_type = 1
-        where g.delete_flag = 0
-            and not exists (select 1 from shieldUser s where s.id = g.id)
-            and not exists (select 1 from second_shield s where s.user_id = #{userId} and s.shield_type = 1 and s.shield_id = g.id and s.delete_flag = 0)
-            and g.goods_status = 3
-            and g.position != '' and g.position is not null
-        order by g.release_time desc
+        SELECT * FROM (
+            SELECT
+                g.id,
+                g.user_id,
+                g.title,
+                g.description,
+                g.price,
+                g.price as amount,
+                g.position,
+                g.like_count,
+                g.collect_count,
+                g.category_one_id,
+                g.category_two_id,
+                g.label,
+                g.topic,
+                g.trade_id,
+                g.created_time,
+                g.created_user_id,
+                g.updated_time,
+                g.updated_user_id,
+                g.release_time,
+                u.user_name,
+                u.user_image,
+                CONCAT('user_', u.user_phone) as user_phone,
+                g.goods_status,
+                g.delete_flag,
+                ROUND(ST_Distance_Sphere(ST_GeomFromText(CONCAT('POINT(', REPLACE(#{position}, ',', ' '), ')' )), ST_GeomFromText(CONCAT('POINT(', REPLACE(g.position, ',', ' '), ')' ))) / 1000, 2) AS dist,
+                g.home_image,
+                g.video_first_frame,
+                case when llr.id is null then '0' else '1' end likeStatus,
+                case when lc.id is null then '0' else '1' end collectStatus,
+                (select count(1) from (SELECT id FROM store_comment c where c.business_id = g.id and c.business_type = 7 and c.delete_flag = 0 UNION ALL
+                SELECT t.id FROM store_comment t INNER JOIN (SELECT id FROM store_comment c where c.business_id = g.id and c.business_type = 7 and c.delete_flag = 0) d ON t.reply_id = d.id and business_type = 6 and delete_flag = 0) a ) as commentCount
+    <!--            (SELECT count(1) FROM store_comment c where c.business_id = g.id and c.business_type = 7 and c.delete_flag = 0 ) as commentCount-->
+            FROM
+                second_goods g inner join life_user u on u.id = g.user_id
+                left join life_like_record llr on llr.dianzan_id = #{phoneId} and llr.huifu_id = g.id and llr.type = 6 and llr.delete_flag = 0
+                left join life_collect lc on lc.business_id = g.id and lc.user_id = #{phoneId} and lc.delete_flag = 0 and lc.business_type = 1
+            where g.delete_flag = 0
+                and not exists (select 1 from shieldUser s where s.id = g.id)
+                and not exists (select 1 from second_shield s where s.user_id = #{userId} and s.shield_type = 1 and s.shield_id = g.id and s.delete_flag = 0)
+                and g.goods_status = 3
+                and g.position != '' and g.position is not null
+            order by g.release_time desc
+        ) a where a.dist &lt; #{radiusKm}
     </select>
 
     <select id="querySecondGoodsDetail" resultType="shop.alien.entity.second.vo.SecondGoodsRecommendVo">

+ 580 - 0
alien-second/doc/委托人信息分页查询接口文档.md

@@ -0,0 +1,580 @@
+# 委托人信息分页查询接口文档(以人为维度)
+
+## 接口概述
+
+### 接口信息
+
+| 项目 | 内容 |
+|------|------|
+| 接口名称 | 委托人信息分页查询(以人为维度) |
+| 接口路径 | `/secondEntrustUser/page` |
+| 请求方式 | POST |
+| 接口版本 | v2.1 |
+| 开发时间 | 2025-11-21 |
+| 接口状态 | ✅ 已完成 |
+
+### 功能描述
+
+用于管理后台或业务系统查询二手交易委托人信息,**以"姓名+身份证号"为唯一维度进行分组统计**,因为同一个人可能有多条委托记录。
+
+**查询维度**:
+- 以委托人的"姓名+身份证号"组合作为唯一标识
+- 对同一个人的多条委托记录进行聚合统计
+- 返回该人的委托记录总数、关联交易总数、最近委托时间等统计信息
+
+**支持查询条件**:
+- 委托人姓名(模糊查询)
+- 身份证号(模糊查询)
+- 委托人电话(模糊查询)
+- **交易编号(模糊查询)** ✨新增
+
+---
+
+## 请求参数
+
+### Request Body (JSON)
+
+| 参数名 | 类型 | 必填 | 描述 | 示例值 | 默认值 |
+|--------|------|------|------|--------|--------|
+| pageNum | Integer | ❌ 否 | 页码(从1开始) | 1 | 1 |
+| pageSize | Integer | ❌ 否 | 每页条数 | 10 | 10 |
+| userName | String | ❌ 否 | 委托人姓名(模糊查询) | 张三 | - |
+| idCard | String | ❌ 否 | 身份证号(模糊查询) | 110101 | - |
+| userPhone | String | ❌ 否 | 委托人电话(模糊查询) | 138 | - |
+| tradeNo | String | ❌ 否 | 交易编号(模糊查询) | S202511 | - |
+
+**参数说明:**
+- `pageNum`: 页码从1开始,传入小于1的值自动修正为1
+- `pageSize`: 每页条数,传入小于1的值自动修正为10
+- `userName`: 支持模糊查询,可输入姓名的任意部分
+- `idCard`: 支持模糊查询,可输入身份证号的任意部分
+- `userPhone`: 支持模糊查询,可输入手机号的任意部分
+- `tradeNo`: 支持模糊查询,可输入交易编号的任意部分
+- **所有查询条件可单独使用或组合使用**
+- **结果按最近委托时间倒序排列,同一个人只显示一条记录**
+
+### 请求示例
+
+#### cURL
+
+```bash
+# 查询所有委托人(第1页,每页10条)
+curl -X POST "http://localhost:8080/secondEntrustUser/page" \
+  -H "Content-Type: application/json" \
+  -H "Authorization: Bearer your_token_here" \
+  -d '{
+    "pageNum": 1,
+    "pageSize": 10
+  }'
+
+# 根据委托人姓名查询
+curl -X POST "http://localhost:8080/secondEntrustUser/page" \
+  -H "Content-Type: application/json" \
+  -H "Authorization: Bearer your_token_here" \
+  -d '{
+    "pageNum": 1,
+    "pageSize": 10,
+    "userName": "张三"
+  }'
+
+# 根据身份证号查询
+curl -X POST "http://localhost:8080/secondEntrustUser/page" \
+  -H "Content-Type: application/json" \
+  -H "Authorization: Bearer your_token_here" \
+  -d '{
+    "pageNum": 1,
+    "pageSize": 10,
+    "idCard": "110101199001011234"
+  }'
+
+# 根据交易编号查询
+curl -X POST "http://localhost:8080/secondEntrustUser/page" \
+  -H "Content-Type: application/json" \
+  -H "Authorization: Bearer your_token_here" \
+  -d '{
+    "pageNum": 1,
+    "pageSize": 10,
+    "tradeNo": "S2025112100001"
+  }'
+
+# 组合查询(姓名+身份证号+交易编号)
+curl -X POST "http://localhost:8080/secondEntrustUser/page" \
+  -H "Content-Type: application/json" \
+  -H "Authorization: Bearer your_token_here" \
+  -d '{
+    "pageNum": 1,
+    "pageSize": 20,
+    "userName": "张",
+    "idCard": "110101",
+    "tradeNo": "S202511"
+  }'
+```
+
+#### JavaScript (Axios)
+
+```javascript
+// 查询委托人列表
+axios.post('/secondEntrustUser/page', {
+  pageNum: 1,
+  pageSize: 10,
+  userName: '张三',
+  idCard: '110101199001011234',
+  tradeNo: 'S2025112100001'
+}, {
+  headers: {
+    'Authorization': 'Bearer your_token_here'
+  }
+})
+.then(response => {
+  const { records, total, pages, current, size } = response.data.data;
+  console.log(`当前第${current}页,每页${size}条`);
+  console.log(`共${total}个委托人,${pages}页`);
+  
+  records.forEach(person => {
+    console.log(`姓名:${person.userName}`);
+    console.log(`身份证:${person.idCard}`);
+    console.log(`委托记录数:${person.entrustCount}`);
+    console.log(`关联交易数:${person.tradeCount}`);
+    console.log(`最近委托时间:${person.latestEntrustTime}`);
+  });
+})
+.catch(error => {
+  console.error(error);
+});
+```
+
+---
+
+## 响应参数
+
+### 响应结构
+
+| 字段名 | 类型 | 描述 |
+|--------|------|------|
+| code | Integer | 响应状态码,200表示成功 |
+| msg | String | 响应消息 |
+| data | IPage&lt;SecondEntrustUserResultVo&gt; | 分页数据对象 |
+
+### IPage 分页对象结构
+
+| 字段名 | 类型 | 描述 | 示例值 |
+|--------|------|------|--------|
+| records | Array&lt;SecondEntrustUserResultVo&gt; | 当前页的委托人列表(去重后) | [...] |
+| total | Long | 总人数(去重后) | 50 |
+| size | Long | 每页条数 | 10 |
+| current | Long | 当前页码 | 1 |
+| pages | Long | 总页数 | 5 |
+
+### SecondEntrustUserResultVo 对象结构(以人为维度)
+
+| 字段名 | 类型 | 是否必返 | 描述 | 示例值 |
+|--------|------|----------|------|--------|
+| id | Integer | ✅ | 该人最小的委托记录ID(用于标识) | 1 |
+| userName | String | ✅ | 委托人姓名 | 张三 |
+| idCard | String | ✅ | 委托人身份证号 | 110101199001011234 |
+| userPhone | String | ✅ | 委托人电话 | 13800138000 |
+| idCardImg | String | ❌ | 委托人身份证照片URL | https://cdn.example.com/idcard/123.jpg |
+| entrustCount | Integer | ✅ | 该人的委托记录总数 | 3 |
+| tradeCount | Integer | ✅ | 该人关联的交易总数 | 3 |
+| latestEntrustTime | String | ✅ | 最近一次委托时间 | 2025-11-21 15:30:00 |
+
+---
+
+## 响应示例
+
+### 成功响应(有数据)
+
+```json
+{
+  "code": 200,
+  "msg": "成功",
+  "data": {
+    "records": [
+      {
+        "id": 1,
+        "userName": "张三",
+        "idCard": "110101199001011234",
+        "userPhone": "13800138000",
+        "idCardImg": "https://cdn.example.com/idcard/123.jpg",
+        "entrustCount": 3,
+        "tradeCount": 3,
+        "latestEntrustTime": "2025-11-21 15:30:00"
+      },
+      {
+        "id": 5,
+        "userName": "李四",
+        "idCard": "110101199002021234",
+        "userPhone": "13900139000",
+        "idCardImg": "https://cdn.example.com/idcard/456.jpg",
+        "entrustCount": 2,
+        "tradeCount": 2,
+        "latestEntrustTime": "2025-11-21 14:20:00"
+      },
+      {
+        "id": 8,
+        "userName": "王五",
+        "idCard": "110101199003031234",
+        "userPhone": "13700137000",
+        "idCardImg": null,
+        "entrustCount": 1,
+        "tradeCount": 1,
+        "latestEntrustTime": "2025-11-21 10:00:00"
+      }
+    ],
+    "total": 50,
+    "size": 10,
+    "current": 1,
+    "pages": 5
+  }
+}
+```
+
+### 空数据响应
+
+```json
+{
+  "code": 200,
+  "msg": "成功",
+  "data": {
+    "records": [],
+    "total": 0,
+    "size": 10,
+    "current": 1,
+    "pages": 0
+  }
+}
+```
+
+### 错误响应
+
+```json
+{
+  "code": 500,
+  "msg": "查询委托人信息列表失败: 数据库连接异常",
+  "data": null
+}
+```
+
+---
+
+## 业务规则
+
+### 查询规则
+
+1. **数据去重**
+   - ✅ 以"姓名+身份证号"为唯一标识进行分组
+   - ✅ 同一个人的多条委托记录聚合为一条显示
+   - ✅ 只返回未删除的记录(`delete_flag = 0`)
+
+2. **统计信息**
+   - `entrustCount`: 统计该人的委托记录总数
+   - `tradeCount`: 统计该人关联的交易总数(去重)
+   - `latestEntrustTime`: 该人最近一次委托的时间
+
+3. **排序规则**
+   - ✅ 按最近委托时间倒序排列(最新的在前)
+   - ✅ 便于查看最近活跃的委托人
+
+4. **查询方式**
+   - ✅ 所有字符串类型条件均支持模糊查询(LIKE)
+   - ✅ 多条件之间为 AND 关系(同时满足)
+   - ✅ 空条件会被忽略
+   - ✅ **交易编号查询:查询该人关联的交易中包含指定编号的记录**
+
+5. **数据关联**
+   - 委托人记录:`second_entrust_user` 表
+   - 交易记录:通过 `trade_id` 关联 `second_trade_record` 表(用于交易编号查询)
+
+---
+
+## 使用场景示例
+
+### 场景1:查看所有委托人列表
+
+```json
+{
+  "pageNum": 1,
+  "pageSize": 10
+}
+```
+
+**返回结果**:按最近委托时间倒序返回所有委托人(去重),每个人只显示一条记录
+
+---
+
+### 场景2:根据姓名搜索委托人
+
+```json
+{
+  "pageNum": 1,
+  "pageSize": 10,
+  "userName": "张三"
+}
+```
+
+**返回结果**:姓名中包含"张三"的所有委托人
+
+---
+
+### 场景3:根据身份证号搜索
+
+```json
+{
+  "pageNum": 1,
+  "pageSize": 10,
+  "idCard": "110101"
+}
+```
+
+**返回结果**:身份证号中包含"110101"的所有委托人
+
+---
+
+### 场景4:根据交易编号搜索委托人
+
+```json
+{
+  "pageNum": 1,
+  "pageSize": 10,
+  "tradeNo": "S2025112100001"
+}
+```
+
+**返回结果**:参与过交易编号包含"S2025112100001"的所有委托人
+
+**说明**:如果张三有3笔交易(S2025112100001、S2025112100002、S2025112100003),当搜索"S2025112100001"时,会返回张三的记录。
+
+---
+
+### 场景5:组合精确查询
+
+```json
+{
+  "pageNum": 1,
+  "pageSize": 10,
+  "userName": "张三",
+  "idCard": "110101199001011234",
+  "tradeNo": "S202511"
+}
+```
+
+**返回结果**:姓名为"张三"、身份证号为"110101199001011234"、且参与过交易编号包含"S202511"的委托人
+
+---
+
+## 前端实现示例
+
+### Vue 3 + Element Plus
+
+```vue
+<template>
+  <div class="entrust-user-page">
+    <!-- 搜索表单 -->
+    <el-form :inline="true" :model="searchForm" class="search-form">
+      <el-form-item label="委托人姓名">
+        <el-input v-model="searchForm.userName" placeholder="请输入姓名" clearable />
+      </el-form-item>
+      <el-form-item label="身份证号">
+        <el-input v-model="searchForm.idCard" placeholder="请输入身份证号" clearable />
+      </el-form-item>
+      <el-form-item label="委托人电话">
+        <el-input v-model="searchForm.userPhone" placeholder="请输入电话" clearable />
+      </el-form-item>
+      <el-form-item label="交易编号">
+        <el-input v-model="searchForm.tradeNo" placeholder="请输入交易编号" clearable />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="handleSearch">查询</el-button>
+        <el-button @click="handleReset">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 数据表格 -->
+    <el-table :data="tableData" border stripe v-loading="loading">
+      <el-table-column prop="userName" label="委托人姓名" width="120" />
+      <el-table-column prop="idCard" label="身份证号" width="180" />
+      <el-table-column prop="userPhone" label="联系电话" width="130" />
+      <el-table-column prop="entrustCount" label="委托记录数" width="120" align="center">
+        <template #default="{ row }">
+          <el-tag>{{ row.entrustCount }} 条</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="tradeCount" label="关联交易数" width="120" align="center">
+        <template #default="{ row }">
+          <el-tag type="success">{{ row.tradeCount }} 笔</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="latestEntrustTime" label="最近委托时间" width="180" />
+      <el-table-column label="操作" width="150" fixed="right">
+        <template #default="{ row }">
+          <el-button type="primary" link @click="viewDetail(row)">查看详情</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页组件 -->
+    <el-pagination
+      v-model:current-page="pageNum"
+      v-model:page-size="pageSize"
+      :page-sizes="[10, 20, 50, 100]"
+      :total="total"
+      layout="total, sizes, prev, pager, next, jumper"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue';
+import { useRouter } from 'vue-router';
+import axios from 'axios';
+import { ElMessage } from 'element-plus';
+
+const router = useRouter();
+const pageNum = ref(1);
+const pageSize = ref(10);
+const total = ref(0);
+const tableData = ref([]);
+const loading = ref(false);
+
+const searchForm = reactive({
+  userName: '',
+  idCard: '',
+  userPhone: '',
+  tradeNo: ''
+});
+
+// 加载数据
+const loadData = async () => {
+  loading.value = true;
+  try {
+    const response = await axios.post('/secondEntrustUser/page', {
+      pageNum: pageNum.value,
+      pageSize: pageSize.value,
+      userName: searchForm.userName || null,
+      idCard: searchForm.idCard || null,
+      userPhone: searchForm.userPhone || null,
+      tradeNo: searchForm.tradeNo || null
+    });
+    
+    const { records, total: totalCount } = response.data.data;
+    tableData.value = records;
+    total.value = totalCount;
+  } catch (error) {
+    ElMessage.error('加载失败:' + error.message);
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 查询
+const handleSearch = () => {
+  pageNum.value = 1;
+  loadData();
+};
+
+// 重置
+const handleReset = () => {
+  searchForm.userName = '';
+  searchForm.idCard = '';
+  searchForm.userPhone = '';
+  searchForm.tradeNo = '';
+  pageNum.value = 1;
+  loadData();
+};
+
+// 每页条数改变
+const handleSizeChange = (newSize) => {
+  pageSize.value = newSize;
+  pageNum.value = 1;
+  loadData();
+};
+
+// 当前页改变
+const handleCurrentChange = (newPage) => {
+  pageNum.value = newPage;
+  loadData();
+};
+
+// 查看详情
+const viewDetail = (row) => {
+  router.push({
+    path: '/entrust-user/detail',
+    query: {
+      userName: row.userName,
+      idCard: row.idCard
+    }
+  });
+};
+
+onMounted(() => {
+  loadData();
+});
+</script>
+```
+
+---
+
+## 错误码说明
+
+| HTTP状态码 | code | msg | 说明 | 解决方案 |
+|-----------|------|-----|------|----------|
+| 200 | 200 | 成功 | 请求成功 | - |
+| 200 | 500 | 查询委托人信息列表失败: xxx | 系统异常 | 查看日志,联系后端开发人员 |
+| 500 | - | - | 服务器内部错误 | 联系后端开发人员 |
+
+---
+
+## 注意事项
+
+### ⚠️ 重要提示
+
+1. **查询维度**
+   - 以"姓名+身份证号"为唯一标识
+   - 同一个人的多条委托记录会聚合为一条显示
+   - 返回该人的统计信息(委托记录数、交易数等)
+
+2. **交易编号查询说明** ✨
+   - 查询该委托人关联的交易中是否包含指定编号
+   - 如果该人有多笔交易,只要其中一笔包含指定编号,就会返回该人的记录
+   - 适用于通过交易编号反查委托人的场景
+
+3. **统计信息说明**
+   - `entrustCount`: 该人的委托记录总数
+   - `tradeCount`: 该人关联的交易总数(已去重)
+   - `latestEntrustTime`: 该人最近一次委托的时间
+
+4. **查看详情**
+   - 点击某个委托人后,使用"姓名+身份证号"查询详情
+   - 详情页面会展示该人所有的委托记录和交易信息
+
+5. **性能优化**
+   - 使用 GROUP BY 进行分组聚合
+   - 统计信息在SQL层面计算,性能优秀
+   - 交易编号查询通过 LEFT JOIN 关联交易表
+
+---
+
+## 版本历史
+
+| 版本 | 日期 | 修改内容 | 修改人 |
+|------|------|----------|--------|
+| v1.0 | 2025-11-21 | 初始版本,按记录查询 | 开发团队 |
+| v2.0 | 2025-11-21 | **重大更新:改为以人为维度查询,支持分组聚合** | 开发团队 |
+| v2.1 | 2025-11-21 | **新增:交易编号查询条件** | 开发团队 |
+
+---
+
+## 联系方式
+
+如有问题或建议,请联系:
+
+- **开发团队**:二手交易平台开发组
+- **服务名称**:alien-second
+- **Controller**:SecondEntrustUserController
+- **文档路径**:`alien-second/doc/委托人信息分页查询接口文档.md`
+
+---
+
+*最后更新时间:2025-11-21(v2.1 - 新增交易编号查询)*

+ 631 - 0
alien-second/doc/委托人详情查询接口文档.md

@@ -0,0 +1,631 @@
+# 委托人详情查询接口文档(以人为维度)
+
+## 接口概述
+
+### 接口信息
+
+| 项目 | 内容 |
+|------|------|
+| 接口名称 | 根据姓名和身份证号获取委托人详情 |
+| 接口路径 | `/secondEntrustUser/detail` |
+| 请求方式 | GET |
+| 接口版本 | v2.0 |
+| 开发时间 | 2025-11-21 |
+| 接口状态 | ✅ 已完成 |
+
+### 功能描述
+
+根据委托人的**姓名和身份证号**获取该人的完整详情信息,**以人为维度聚合查询所有相关数据**,包含:
+- 委托人基本信息
+- **该人所有委托记录关联的所有交易记录列表**
+- 每条交易记录包含:
+  - 交易基本信息(交易编号、金额、状态等)
+  - 买卖双方信息(姓名、电话)
+  - **交易操作节点信息(交易流程)**
+  - 商品信息
+
+**查询逻辑**:
+1. 根据"姓名+身份证号"查询该人的所有委托记录
+2. 收集所有委托记录关联的交易ID
+3. 批量查询所有交易记录及详细信息
+4. 为每笔交易添加操作节点信息(参考 `/admin/secondGoods/detail` 接口逻辑)
+
+---
+
+## 请求参数
+
+### Query Parameters
+
+| 参数名 | 类型 | 必填 | 描述 | 示例值 |
+|--------|------|------|------|--------|
+| userName | String | ✅ 是 | 委托人姓名 | 张三 |
+| idCard | String | ✅ 是 | 委托人身份证号 | 110101199001011234 |
+
+### 请求示例
+
+#### cURL
+
+```bash
+# 获取委托人详情
+curl -X GET "http://localhost:8080/secondEntrustUser/detail?userName=张三&idCard=110101199001011234" \
+  -H "Authorization: Bearer your_token_here"
+```
+
+#### JavaScript (Axios)
+
+```javascript
+// 获取委托人详情
+axios.get('/secondEntrustUser/detail', {
+  params: {
+    userName: '张三',
+    idCard: '110101199001011234'
+  },
+  headers: {
+    'Authorization': 'Bearer your_token_here'
+  }
+})
+.then(response => {
+  const { entrustUserInfo, tradeRecords } = response.data.data;
+  console.log('委托人信息:', entrustUserInfo);
+  console.log('该人所有交易记录:', tradeRecords);
+  console.log('交易总数:', tradeRecords.length);
+})
+.catch(error => {
+  console.error(error);
+});
+```
+
+#### Java (RestTemplate)
+
+```java
+String url = "http://localhost:8080/secondEntrustUser/detail" +
+             "?userName=张三&idCard=110101199001011234";
+RestTemplate restTemplate = new RestTemplate();
+R<SecondEntrustUserDetailVo> result = restTemplate.getForObject(url, R.class);
+
+SecondEntrustUserDetailVo detailVo = result.getData();
+SecondEntrustUser entrustUserInfo = detailVo.getEntrustUserInfo();
+List<SecondTradeRecordVo> tradeRecords = detailVo.getTradeRecords();
+
+System.out.println("委托人姓名:" + entrustUserInfo.getUserName());
+System.out.println("该人关联的交易总数:" + tradeRecords.size());
+```
+
+---
+
+## 响应参数
+
+### 响应结构
+
+| 字段名 | 类型 | 描述 |
+|--------|------|------|
+| code | Integer | 响应状态码,200表示成功 |
+| msg | String | 响应消息 |
+| data | SecondEntrustUserDetailVo | 委托人详情对象 |
+
+### SecondEntrustUserDetailVo 对象结构
+
+| 字段名 | 类型 | 描述 |
+|--------|------|------|
+| entrustUserInfo | SecondEntrustUser | 委托人基本信息(该人的第一条记录) |
+| tradeRecords | Array&lt;SecondTradeRecordVo&gt; | 该人所有委托记录关联的交易记录集合 |
+
+### SecondEntrustUser 委托人信息结构
+
+| 字段名 | 类型 | 是否必返 | 描述 | 示例值 |
+|--------|------|----------|------|--------|
+| id | Integer | ✅ | 委托人记录ID | 1 |
+| tradeId | Integer | ✅ | 关联的交易ID | 100 |
+| userName | String | ✅ | 委托人姓名 | 张三 |
+| userPhone | String | ✅ | 委托人电话 | 13800138000 |
+| idCard | String | ✅ | 委托人身份证号 | 110101199001011234 |
+| idCardImg | String | ❌ | 委托人身份证照片URL | https://cdn.example.com/idcard/123.jpg |
+| deleteFlag | Integer | ✅ | 删除标记(0:未删除,1:已删除) | 0 |
+| createdTime | Date | ✅ | 创建时间 | 2025-11-21 10:00:00 |
+| updatedTime | Date | ✅ | 更新时间 | 2025-11-21 10:00:00 |
+
+### SecondTradeRecordVo 交易记录结构
+
+#### 交易基本信息
+
+| 字段名 | 类型 | 是否必返 | 描述 | 示例值 |
+|--------|------|----------|------|--------|
+| id | Integer | ✅ | 交易记录ID | 100 |
+| tradeNo | String | ✅ | 交易编号 | S2025112100001 |
+| goodsId | Integer | ✅ | 商品ID | 200 |
+| goodsRecordId | Integer | ✅ | 商品记录ID | 300 |
+| transactionAmount | BigDecimal | ✅ | 交易金额(元) | 3800.00 |
+| tradeStatus | Integer | ✅ | 交易状态 | 4 |
+| transactionTime | Date | ❌ | 交易时间 | 2025-11-21 14:00:00 |
+| transactionLocation | String | ❌ | 交易地点 | 北京市朝阳区xxx |
+| createdTime | Date | ✅ | 创建时间 | 2025-11-21 10:00:00 |
+
+#### 买卖双方信息
+
+| 字段名 | 类型 | 是否必返 | 描述 | 示例值 |
+|--------|------|----------|------|--------|
+| buyerId | Integer | ✅ | 买方ID | 456 |
+| buyerName | String | ❌ | 买方姓名 | 李四 |
+| buyerPhone | String | ❌ | 买方电话 | 13900139000 |
+| sellerId | Integer | ✅ | 卖方ID | 789 |
+| sellerName | String | ❌ | 卖方姓名 | 王五 |
+| sellerPhone | String | ❌ | 卖方电话 | 13700137000 |
+
+#### 交易操作节点信息
+
+| 字段名 | 类型 | 是否必返 | 描述 |
+|--------|------|----------|------|
+| operationJsonList | Array&lt;JSONObject&gt; | ✅ | 交易操作节点列表,展示完整交易流程 |
+
+每个操作节点包含:
+- `operationType`: 操作类型
+- `operationTime`: 操作时间
+- `operationUserId`: 操作人ID
+- `operationUserName`: 操作人姓名
+- `operationDesc`: 操作描述
+
+---
+
+## 响应示例
+
+### 成功响应(张三有3条委托记录)
+
+```json
+{
+  "code": 200,
+  "msg": "成功",
+  "data": {
+    "entrustUserInfo": {
+      "id": 1,
+      "tradeId": 100,
+      "userName": "张三",
+      "userPhone": "13800138000",
+      "idCard": "110101199001011234",
+      "idCardImg": "https://cdn.example.com/idcard/123.jpg",
+      "deleteFlag": 0,
+      "createdTime": "2025-11-21 10:00:00",
+      "updatedTime": "2025-11-21 10:00:00"
+    },
+    "tradeRecords": [
+      {
+        "id": 102,
+        "tradeNo": "S2025112100003",
+        "goodsId": 202,
+        "goodsRecordId": 302,
+        "transactionAmount": 2800.00,
+        "tradeStatus": 4,
+        "transactionTime": "2025-11-21 15:30:00",
+        "transactionLocation": "北京市海淀区xxx",
+        "buyerId": 456,
+        "buyerName": "李四",
+        "buyerPhone": "13900139000",
+        "sellerId": 789,
+        "sellerName": "王五",
+        "sellerPhone": "13700137000",
+        "createdTime": "2025-11-21 15:00:00",
+        "operationJsonList": [
+          {
+            "operationType": "CREATE",
+            "operationTime": "2025-11-21 15:00:00",
+            "operationUserId": 456,
+            "operationUserName": "李四",
+            "operationDesc": "创建交易"
+          },
+          {
+            "operationType": "COMPLETE",
+            "operationTime": "2025-11-21 15:30:00",
+            "operationUserId": 456,
+            "operationUserName": "李四",
+            "operationDesc": "完成交易"
+          }
+        ]
+      },
+      {
+        "id": 101,
+        "tradeNo": "S2025112100002",
+        "goodsId": 201,
+        "goodsRecordId": 301,
+        "transactionAmount": 4500.00,
+        "tradeStatus": 4,
+        "transactionTime": "2025-11-21 14:00:00",
+        "transactionLocation": "北京市朝阳区xxx",
+        "buyerId": 457,
+        "buyerName": "赵六",
+        "buyerPhone": "13600136000",
+        "sellerId": 788,
+        "sellerName": "孙七",
+        "sellerPhone": "13500135000",
+        "createdTime": "2025-11-21 13:30:00",
+        "operationJsonList": [
+          {
+            "operationType": "CREATE",
+            "operationTime": "2025-11-21 13:30:00",
+            "operationUserId": 457,
+            "operationUserName": "赵六",
+            "operationDesc": "创建交易"
+          },
+          {
+            "operationType": "COMPLETE",
+            "operationTime": "2025-11-21 14:00:00",
+            "operationUserId": 457,
+            "operationUserName": "赵六",
+            "operationDesc": "完成交易"
+          }
+        ]
+      },
+      {
+        "id": 100,
+        "tradeNo": "S2025112100001",
+        "goodsId": 200,
+        "goodsRecordId": 300,
+        "transactionAmount": 3800.00,
+        "tradeStatus": 4,
+        "transactionTime": "2025-11-21 10:00:00",
+        "transactionLocation": "北京市东城区xxx",
+        "buyerId": 455,
+        "buyerName": "周八",
+        "buyerPhone": "13400134000",
+        "sellerId": 787,
+        "sellerName": "吴九",
+        "sellerPhone": "13300133000",
+        "createdTime": "2025-11-21 09:30:00",
+        "operationJsonList": [
+          {
+            "operationType": "CREATE",
+            "operationTime": "2025-11-21 09:30:00",
+            "operationUserId": 455,
+            "operationUserName": "周八",
+            "operationDesc": "创建交易"
+          },
+          {
+            "operationType": "COMPLETE",
+            "operationTime": "2025-11-21 10:00:00",
+            "operationUserId": 455,
+            "operationUserName": "周八",
+            "operationDesc": "完成交易"
+          }
+        ]
+      }
+    ]
+  }
+}
+```
+
+**说明**:张三(身份证号 110101199001011234)有3条委托记录,关联了3笔交易,接口返回了他所有的交易信息,按创建时间倒序排列。
+
+### 委托人不存在
+
+```json
+{
+  "code": 500,
+  "msg": "委托人信息不存在",
+  "data": null
+}
+```
+
+### 参数缺失
+
+```json
+{
+  "code": 500,
+  "msg": "委托人姓名不能为空",
+  "data": null
+}
+```
+
+---
+
+## 业务规则
+
+### 查询规则
+
+1. **查询维度**
+   - ✅ 以"姓名+身份证号"为唯一标识
+   - ✅ 查询该人的所有委托记录
+   - ✅ 聚合该人所有委托记录关联的交易信息
+
+2. **数据聚合**
+   - 根据姓名和身份证号查询所有委托记录
+   - 收集所有委托记录的 `tradeId`
+   - 批量查询所有交易记录(自动去重)
+   - 只返回未删除的记录(`delete_flag = 0`)
+
+3. **交易记录排序**
+   - ✅ 按交易创建时间倒序排列(最新的在前)
+
+4. **交易详情处理**
+   - ✅ 每条交易记录包含完整的买卖双方信息
+   - ✅ **自动加载交易操作节点信息(参考商品详情接口逻辑)**
+   - ✅ 操作节点按时间顺序排列,展示完整交易流程
+
+5. **数据关联**
+   - 委托人记录:从 `second_entrust_user` 表根据姓名和身份证号查询
+   - 交易记录:从 `second_trade_record` 表根据 `tradeId` 列表查询
+   - 买卖双方:通过 `buyer_id` 和 `seller_id` 关联 `life_user` 表
+   - 操作节点:从 `second_trade_operation` 表获取交易流程
+
+---
+
+## 交易状态说明
+
+### 交易状态 (tradeStatus)
+
+| 状态值 | 状态名称 | 说明 |
+|--------|----------|------|
+| 1 | 待确认 | 买家发起交易请求,等待卖家确认 |
+| 2 | 已拒绝 | 卖家拒绝了交易请求 |
+| 3 | 待交易 | 双方确认交易,等待线下交易 |
+| 4 | 交易成功 | 交易完成,双方确认成功 |
+| 5 | 交易失败 | 交易失败 |
+| 6 | 交易取消 | 交易被取消 |
+
+---
+
+## 使用场景
+
+### 场景:查看某个委托人的完整交易历史
+
+从分页列表点击某个委托人后,使用该接口获取该人的完整信息。
+
+**步骤**:
+1. 用户在分页列表看到"张三(110101199001011234)有3条委托记录"
+2. 点击"查看详情"
+3. 调用接口:`GET /secondEntrustUser/detail?userName=张三&idCard=110101199001011234`
+4. 返回张三的基本信息和他所有的交易记录(3笔)
+5. 每笔交易都包含完整的交易流程节点
+
+**应用场景**:
+- 委托人详情页展示
+- 交易历史查询
+- 纠纷处理和审核
+- 数据分析和统计
+
+---
+
+## 前端实现示例
+
+### Vue 3 + Element Plus
+
+```vue
+<template>
+  <div class="entrust-user-detail">
+    <el-page-header @back="goBack" title="返回列表">
+      <template #content>
+        <span class="page-title">委托人详情</span>
+      </template>
+    </el-page-header>
+
+    <!-- 委托人基本信息 -->
+    <el-card class="info-card" v-if="entrustUserInfo" style="margin-top: 20px;">
+      <template #header>
+        <div class="card-header">
+          <span>委托人信息</span>
+        </div>
+      </template>
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="姓名">
+          {{ entrustUserInfo.userName }}
+        </el-descriptions-item>
+        <el-descriptions-item label="身份证号">
+          {{ entrustUserInfo.idCard }}
+        </el-descriptions-item>
+        <el-descriptions-item label="联系电话">
+          {{ entrustUserInfo.userPhone }}
+        </el-descriptions-item>
+        <el-descriptions-item label="首次委托时间">
+          {{ entrustUserInfo.createdTime }}
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-card>
+
+    <!-- 交易记录列表 -->
+    <el-card class="trade-card" style="margin-top: 20px;">
+      <template #header>
+        <div class="card-header">
+          <span>关联交易记录(共 {{ tradeRecords.length }} 笔)</span>
+        </div>
+      </template>
+      
+      <el-timeline v-if="tradeRecords.length > 0">
+        <el-timeline-item
+          v-for="trade in tradeRecords"
+          :key="trade.id"
+          :timestamp="trade.createdTime"
+          placement="top"
+        >
+          <el-card>
+            <h4>交易编号:{{ trade.tradeNo }}</h4>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <p>交易金额:<el-tag type="danger">¥{{ trade.transactionAmount }}</el-tag></p>
+                <p>交易状态:<el-tag :type="getStatusType(trade.tradeStatus)">
+                  {{ getTradeStatusText(trade.tradeStatus) }}
+                </el-tag></p>
+              </el-col>
+              <el-col :span="12">
+                <p>买方:{{ trade.buyerName }}({{ trade.buyerPhone }})</p>
+                <p>卖方:{{ trade.sellerName }}({{ trade.sellerPhone }})</p>
+              </el-col>
+            </el-row>
+            
+            <!-- 交易操作流程 -->
+            <el-divider content-position="left">交易流程</el-divider>
+            <el-steps
+              :active="trade.operationJsonList.length"
+              finish-status="success"
+              align-center
+            >
+              <el-step
+                v-for="(operation, index) in trade.operationJsonList"
+                :key="index"
+                :title="operation.operationDesc"
+                :description="`${operation.operationTime} - ${operation.operationUserName}`"
+              />
+            </el-steps>
+          </el-card>
+        </el-timeline-item>
+      </el-timeline>
+      
+      <el-empty v-else description="暂无交易记录" />
+    </el-card>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import axios from 'axios';
+import { ElMessage } from 'element-plus';
+
+const route = useRoute();
+const router = useRouter();
+const userName = ref(route.query.userName);
+const idCard = ref(route.query.idCard);
+const entrustUserInfo = ref(null);
+const tradeRecords = ref([]);
+const loading = ref(false);
+
+// 交易状态映射
+const tradeStatusMap = {
+  1: '待确认',
+  2: '已拒绝',
+  3: '待交易',
+  4: '交易成功',
+  5: '交易失败',
+  6: '交易取消'
+};
+
+const getTradeStatusText = (status) => {
+  return tradeStatusMap[status] || '未知状态';
+};
+
+const getStatusType = (status) => {
+  const typeMap = {
+    1: 'info',
+    2: 'danger',
+    3: 'warning',
+    4: 'success',
+    5: 'danger',
+    6: 'info'
+  };
+  return typeMap[status] || 'info';
+};
+
+const goBack = () => {
+  router.back();
+};
+
+// 加载委托人详情
+const loadDetail = async () => {
+  loading.value = true;
+  try {
+    const response = await axios.get('/secondEntrustUser/detail', {
+      params: {
+        userName: userName.value,
+        idCard: idCard.value
+      }
+    });
+    
+    const data = response.data.data;
+    entrustUserInfo.value = data.entrustUserInfo;
+    tradeRecords.value = data.tradeRecords || [];
+  } catch (error) {
+    ElMessage.error('加载失败:' + error.message);
+  } finally {
+    loading.value = false;
+  }
+};
+
+onMounted(() => {
+  if (userName.value && idCard.value) {
+    loadDetail();
+  } else {
+    ElMessage.error('缺少必要参数');
+    router.back();
+  }
+});
+</script>
+
+<style scoped>
+.entrust-user-detail {
+  padding: 20px;
+}
+
+.page-title {
+  font-size: 18px;
+  font-weight: bold;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+</style>
+```
+
+---
+
+## 错误码说明
+
+| HTTP状态码 | code | msg | 说明 | 解决方案 |
+|-----------|------|-----|------|----------|
+| 200 | 200 | 成功 | 请求成功 | - |
+| 200 | 500 | 委托人信息不存在 | 该姓名和身份证号组合不存在 | 检查参数是否正确 |
+| 200 | 500 | 委托人姓名不能为空 | 未传入姓名参数 | 必须传入userName参数 |
+| 200 | 500 | 委托人身份证号不能为空 | 未传入身份证号参数 | 必须传入idCard参数 |
+| 200 | 500 | 获取委托人详情失败: xxx | 系统异常 | 查看日志,联系后端开发人员 |
+| 500 | - | - | 服务器内部错误 | 联系后端开发人员 |
+
+---
+
+## 注意事项
+
+### ⚠️ 重要提示
+
+1. **必传参数**
+   - `userName` 和 `idCard` 都是必传参数
+   - 两个参数必须同时传入且正确匹配
+
+2. **查询维度**
+   - 以"姓名+身份证号"为唯一标识
+   - 返回该人所有委托记录关联的所有交易信息
+   - 同一个人可能有多笔交易
+
+3. **交易记录**
+   - 交易记录按创建时间倒序排列
+   - 只返回未删除的交易记录
+   - 每笔交易都包含完整的操作节点信息
+
+4. **操作节点信息**
+   - 参考了 `/admin/secondGoods/detail` 接口的实现逻辑
+   - 展示完整的交易流程
+   - 包含操作时间、操作人、操作描述等信息
+
+5. **性能考虑**
+   - 如果该人关联的交易记录过多,可能影响响应速度
+   - 建议在前端做分页展示或懒加载
+
+---
+
+## 版本历史
+
+| 版本 | 日期 | 修改内容 | 修改人 |
+|------|------|----------|--------|
+| v1.0 | 2025-11-21 | 初始版本,根据主键ID查询 | 开发团队 |
+| v2.0 | 2025-11-21 | **重大更新:改为根据姓名+身份证号查询,以人为维度聚合交易** | 开发团队 |
+
+---
+
+## 联系方式
+
+如有问题或建议,请联系:
+
+- **开发团队**:二手交易平台开发组
+- **服务名称**:alien-second
+- **Controller**:SecondEntrustUserController
+- **文档路径**:`alien-second/doc/委托人详情查询接口文档.md`
+
+---
+
+*最后更新时间:2025-11-21(v2.0 - 以人为维度)*

+ 582 - 0
alien-second/doc/获取卖家交易评价列表接口文档.md

@@ -0,0 +1,582 @@
+# 获取卖家交易评价列表接口文档(分页版)
+
+## 接口概述
+
+### 接口信息
+
+| 项目 | 内容 |
+|------|------|
+| 接口名称 | 获取用户作为卖家的交易评价列表(分页) |
+| 接口路径 | `/secondTradeRecord/getSellerEvaluationList` |
+| 请求方式 | GET |
+| 接口版本 | v1.1(支持分页) |
+| 开发时间 | 2025-11-18 |
+| 接口状态 | ✅ 已完成 |
+
+### 功能描述
+
+用于用户主页展示该用户作为卖家收到的所有交易评价,**支持分页查询**,包含:
+- 买家完整信息(昵称、头像、真实姓名、性别、简介等)
+- 评价内容和评分
+- 买家和卖家的信用积分
+- 买家和卖家的风控评分等级
+- 交易商品信息
+- 交易基本信息
+
+---
+
+## 请求参数
+
+### Query Parameters
+
+| 参数名 | 类型 | 必填 | 描述 | 示例值 | 默认值 |
+|--------|------|------|------|--------|--------|
+| sellerId | Integer | ✅ 是 | 卖家用户ID(主页用户ID) | 123 | - |
+| pageNum | Integer | ❌ 否 | 页码(从1开始) | 1 | 1 |
+| pageSize | Integer | ❌ 否 | 每页条数(最大100) | 10 | 10 |
+
+**参数说明:**
+- `pageNum`: 页码从1开始,传入小于1的值自动修正为1
+- `pageSize`: 每页条数,传入小于1的值自动修正为10,超过100自动限制为100
+
+### 请求示例
+
+#### cURL
+
+```bash
+# 获取第1页,每页10条(使用默认值)
+curl -X GET "http://localhost:8080/secondTradeRecord/getSellerEvaluationList?sellerId=123" \
+  -H "Authorization: Bearer your_token_here"
+
+# 获取第2页,每页20条
+curl -X GET "http://localhost:8080/secondTradeRecord/getSellerEvaluationList?sellerId=123&pageNum=2&pageSize=20" \
+  -H "Authorization: Bearer your_token_here"
+```
+
+#### JavaScript (Axios)
+
+```javascript
+// 基本用法
+axios.get('/secondTradeRecord/getSellerEvaluationList', {
+  params: {
+    sellerId: 123,
+    pageNum: 1,
+    pageSize: 10
+  },
+  headers: {
+    'Authorization': 'Bearer your_token_here'
+  }
+})
+.then(response => {
+  const { records, total, pages, current, size } = response.data.data;
+  console.log(`当前第${current}页,每页${size}条`);
+  console.log(`共${total}条数据,${pages}页`);
+  console.log('评价列表:', records);
+})
+.catch(error => {
+  console.error(error);
+});
+
+// 结合分页组件使用
+function loadEvaluations(sellerId, pageNum = 1, pageSize = 10) {
+  return axios.get('/secondTradeRecord/getSellerEvaluationList', {
+    params: { sellerId, pageNum, pageSize }
+  });
+}
+
+// 加载下一页
+function loadNextPage(currentPage) {
+  loadEvaluations(123, currentPage + 1, 10)
+    .then(response => {
+      // 处理数据
+    });
+}
+```
+
+#### Java (RestTemplate)
+
+```java
+String url = "http://localhost:8080/secondTradeRecord/getSellerEvaluationList" +
+             "?sellerId=123&pageNum=1&pageSize=10";
+RestTemplate restTemplate = new RestTemplate();
+R<IPage<SellerEvaluationVo>> result = restTemplate.getForObject(url, R.class);
+
+IPage<SellerEvaluationVo> page = result.getData();
+System.out.println("总记录数:" + page.getTotal());
+System.out.println("总页数:" + page.getPages());
+System.out.println("当前页:" + page.getCurrent());
+System.out.println("每页条数:" + page.getSize());
+List<SellerEvaluationVo> records = page.getRecords();
+```
+
+---
+
+## 响应参数
+
+### 响应结构
+
+| 字段名 | 类型 | 描述 |
+|--------|------|------|
+| code | Integer | 响应状态码,200表示成功 |
+| msg | String | 响应消息 |
+| data | IPage&lt;SellerEvaluationVo&gt; | 分页数据对象 |
+
+### IPage 分页对象结构
+
+| 字段名 | 类型 | 描述 | 示例值 |
+|--------|------|------|--------|
+| records | Array&lt;SellerEvaluationVo&gt; | 当前页的评价列表 | [...] |
+| total | Long | 总记录数 | 45 |
+| size | Long | 每页条数 | 10 |
+| current | Long | 当前页码 | 1 |
+| pages | Long | 总页数 | 5 |
+
+### SellerEvaluationVo 对象结构
+
+#### 交易基本信息
+
+| 字段名 | 类型 | 是否必返 | 描述 | 示例值 |
+|--------|------|----------|------|--------|
+| id | Integer | ✅ | 交易记录ID | 1 |
+| tradeNo | String | ✅ | 交易编号 | S2025111800001 |
+| transactionTime | String | ✅ | 交易时间 | 2025-11-18 10:00:00 |
+| transactionAmount | BigDecimal | ✅ | 交易金额(元) | 3800.00 |
+| tradeStatus | Integer | ✅ | 交易状态 | 4 |
+| createdTime | String | ✅ | 创建时间 | 2025-11-18 09:00:00 |
+
+#### 商品信息
+
+| 字段名 | 类型 | 是否必返 | 描述 | 示例值 |
+|--------|------|----------|------|--------|
+| goodsId | Integer | ✅ | 商品ID | 100 |
+| goodsTitle | String | ❌ | 商品标题 | 二手iPhone 13 128G 白色 95新 |
+| goodsHomeImage | String | ❌ | 商品封面图片URL | https://cdn.example.com/goods/iphone13.jpg |
+| goodsPrice | BigDecimal | ❌ | 商品价格(元) | 3999.00 |
+
+#### 买家信息
+
+| 字段名 | 类型 | 是否必返 | 描述 | 示例值 |
+|--------|------|----------|------|--------|
+| buyerId | Integer | ✅ | 买家ID | 456 |
+| buyerName | String | ❌ | 买家昵称 | 张三 |
+| buyerImage | String | ❌ | 买家头像URL | https://cdn.example.com/avatar/user456.jpg |
+| buyerPhone | String | ❌ | 买家手机号 | 13800138000 |
+| buyerRealName | String | ❌ | 买家真实姓名 | 张三三 |
+| buyerSex | String | ❌ | 买家性别 | 男 |
+| buyerJianjie | String | ❌ | 买家简介 | 热爱数码产品,诚信交易 |
+
+#### 买家评价信息
+
+| 字段名 | 类型 | 是否必返 | 描述 | 示例值 |
+|--------|------|----------|------|--------|
+| buyerEvaluate | String | ✅ | 买家评价内容 | 商品质量很好,和描述一致,卖家态度也不错,推荐! |
+| buyerRating | Integer | ❌ | 买家评分(1-5分) | 5 |
+| buyerCreditScore | Integer | ✅ | 买家信用积分 | 450 |
+| buyerCreditLevel | String | ✅ | 买家风控评分等级 | A |
+
+#### 卖家信息
+
+| 字段名 | 类型 | 是否必返 | 描述 | 示例值 |
+|--------|------|----------|------|--------|
+| sellerId | Integer | ✅ | 卖家ID | 123 |
+| sellerName | String | ❌ | 卖家昵称 | 李四 |
+| sellerImage | String | ❌ | 卖家头像URL | https://cdn.example.com/seller.jpg |
+| sellerCreditScore | Integer | ✅ | 卖家信用积分 | 500 |
+| sellerCreditLevel | String | ✅ | 卖家风控评分等级 | O |
+
+---
+
+## 枚举说明
+
+### 交易状态 (tradeStatus)
+
+| 状态值 | 状态名称 | 说明 |
+|--------|----------|------|
+| 1 | 待确认 | 买家发起交易请求,等待卖家确认 |
+| 2 | 已拒绝 | 卖家拒绝了交易请求 |
+| 3 | 待交易 | 双方确认交易,等待线下交易 |
+| 4 | 交易成功 | 交易完成,双方确认成功 |
+| 5 | 交易失败 | 交易失败 |
+| 6 | 交易取消 | 交易被取消 |
+
+### 风控评分等级 (creditLevel)
+
+| 等级 | 违规次数 | 等级说明 | 风险程度 |
+|------|----------|----------|----------|
+| O | 0次 | 无违规记录 | 🟢 无风险(最优) |
+| A | 1-2次 | 轻微违规 | 🟢 低风险 |
+| B | 3-5次 | 一般违规 | 🟡 中等风险 |
+| C | 6-9次 | 较严重违规 | 🟡 较高风险 |
+| D | 10-14次 | 严重违规 | 🔴 高风险 |
+| E | ≥15次 | 极差 | 🔴 极高风险(最差) |
+
+---
+
+## 响应示例
+
+### 成功响应(有数据)
+
+```json
+{
+  "code": 200,
+  "msg": "成功",
+  "data": {
+    "records": [
+      {
+        "id": 1,
+        "tradeNo": "S2025111800001",
+        "goodsId": 100,
+        "goodsTitle": "二手iPhone 13 128G 白色 95新",
+        "goodsHomeImage": "https://cdn.example.com/goods/iphone13.jpg",
+        "goodsPrice": 3999.00,
+        "transactionAmount": 3800.00,
+        "buyerId": 456,
+        "buyerName": "张三",
+        "buyerImage": "https://cdn.example.com/avatar/user456.jpg",
+        "buyerPhone": "13800138000",
+        "buyerRealName": "张三三",
+        "buyerSex": "男",
+        "buyerJianjie": "热爱数码产品,诚信交易",
+        "buyerEvaluate": "商品质量很好,和描述一致,卖家态度也不错,推荐!",
+        "buyerRating": 5,
+        "buyerCreditScore": 450,
+        "buyerCreditLevel": "A",
+        "sellerId": 123,
+        "sellerName": "李四",
+        "sellerImage": "https://cdn.example.com/avatar/user123.jpg",
+        "sellerCreditScore": 500,
+        "sellerCreditLevel": "O",
+        "transactionTime": "2025-11-18 10:00:00",
+        "tradeStatus": 4,
+        "createdTime": "2025-11-18 09:00:00"
+      }
+    ],
+    "total": 45,
+    "size": 10,
+    "current": 1,
+    "pages": 5
+  }
+}
+```
+
+### 空数据响应
+
+```json
+{
+  "code": 200,
+  "msg": "成功",
+  "data": {
+    "records": [],
+    "total": 0,
+    "size": 10,
+    "current": 1,
+    "pages": 0
+  }
+}
+```
+
+### 错误响应
+
+#### 参数缺失
+
+```json
+{
+  "code": 500,
+  "msg": "获取评价列表失败: 卖家ID不能为空",
+  "data": null
+}
+```
+
+#### 系统异常
+
+```json
+{
+  "code": 500,
+  "msg": "获取评价列表失败: 数据库连接异常",
+  "data": null
+}
+```
+
+---
+
+## 分页计算说明
+
+### 分页参数
+
+- **pageNum**: 当前页码,从 1 开始
+- **pageSize**: 每页显示条数
+- **total**: 总记录数
+- **pages**: 总页数 = Math.ceil(total / pageSize)
+
+### 分页示例
+
+假设某卖家有 45 条评价:
+
+| pageNum | pageSize | 返回记录 | 页面显示 |
+|---------|----------|----------|----------|
+| 1 | 10 | 1-10 | 第1页,共5页,显示10条 |
+| 2 | 10 | 11-20 | 第2页,共5页,显示10条 |
+| 3 | 10 | 21-30 | 第3页,共5页,显示10条 |
+| 4 | 10 | 31-40 | 第4页,共5页,显示10条 |
+| 5 | 10 | 41-45 | 第5页,共5页,显示5条 |
+
+---
+
+## 业务规则
+
+### 查询规则
+
+1. **数据过滤**
+   - ✅ 只返回有买家评价内容的记录(`buyer_evaluate IS NOT NULL AND buyer_evaluate != ''`)
+   - ✅ 只返回未删除的记录(`delete_flag = 0`)
+   - ✅ 按创建时间倒序排列(最新评价在前)
+
+2. **分页限制**
+   - ✅ pageNum < 1 时自动修正为 1
+   - ✅ pageSize < 1 时自动修正为 10
+   - ✅ pageSize > 100 时自动限制为 100(防止数据量过大)
+
+3. **数据关联**
+   - 商品信息:通过 `goods_record_id` 关联 `second_goods_record` 表
+   - 买家信息:通过 `buyer_id` 关联 `life_user` 表
+   - 卖家信息:通过 `seller_id` 关联 `life_user` 表
+   - 信用积分:通过 `user_id` 关联 `second_user_credit` 表
+   - 风控等级:实时计算,查询 `second_risk_control_record` 表
+
+4. **性能优化**
+   - 使用 MyBatis Plus 分页插件
+   - 批量查询避免 N+1 问题
+   - 关联数据使用 Map 索引,时间复杂度 O(1)
+   - 只查询当前页的交易记录,再批量关联其他信息
+
+---
+
+## 错误码说明
+
+| HTTP状态码 | code | msg | 说明 | 解决方案 |
+|-----------|------|-----|------|----------|
+| 200 | 200 | 成功 | 请求成功 | - |
+| 200 | 500 | 卖家ID不能为空 | 未传入 sellerId 参数 | 检查请求参数,确保传入 sellerId |
+| 200 | 500 | 获取评价列表失败: xxx | 系统异常 | 查看日志,联系后端开发人员 |
+| 500 | - | - | 服务器内部错误 | 联系后端开发人员 |
+
+---
+
+## 前端分页实现示例
+
+### Vue 3 + Element Plus
+
+```vue
+<template>
+  <div class="evaluation-list">
+    <!-- 评价列表 -->
+    <div v-for="item in evaluations" :key="item.id" class="evaluation-item">
+      <div class="buyer-info">
+        <img :src="item.buyerImage" :alt="item.buyerName" />
+        <span>{{ item.buyerName }}</span>
+        <el-rate :model-value="item.buyerRating" disabled />
+      </div>
+      <div class="evaluation-content">{{ item.buyerEvaluate }}</div>
+      <div class="evaluation-time">{{ item.createdTime }}</div>
+    </div>
+    
+    <!-- 分页组件 -->
+    <el-pagination
+      v-model:current-page="pageNum"
+      v-model:page-size="pageSize"
+      :page-sizes="[10, 20, 50, 100]"
+      :total="total"
+      layout="total, sizes, prev, pager, next, jumper"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import axios from 'axios';
+
+const sellerId = ref(123);
+const pageNum = ref(1);
+const pageSize = ref(10);
+const total = ref(0);
+const evaluations = ref([]);
+
+// 加载评价列表
+const loadEvaluations = async () => {
+  try {
+    const response = await axios.get('/secondTradeRecord/getSellerEvaluationList', {
+      params: {
+        sellerId: sellerId.value,
+        pageNum: pageNum.value,
+        pageSize: pageSize.value
+      }
+    });
+    
+    const { records, total: totalCount } = response.data.data;
+    evaluations.value = records;
+    total.value = totalCount;
+  } catch (error) {
+    console.error('加载失败:', error);
+  }
+};
+
+// 每页条数改变
+const handleSizeChange = (newSize) => {
+  pageSize.value = newSize;
+  pageNum.value = 1; // 重置到第一页
+  loadEvaluations();
+};
+
+// 当前页改变
+const handleCurrentChange = (newPage) => {
+  pageNum.value = newPage;
+  loadEvaluations();
+};
+
+onMounted(() => {
+  loadEvaluations();
+});
+</script>
+```
+
+### React + Ant Design
+
+```jsx
+import React, { useState, useEffect } from 'react';
+import { Pagination, Rate, Avatar, List } from 'antd';
+import axios from 'axios';
+
+function SellerEvaluationList({ sellerId }) {
+  const [pageNum, setPageNum] = useState(1);
+  const [pageSize, setPageSize] = useState(10);
+  const [total, setTotal] = useState(0);
+  const [evaluations, setEvaluations] = useState([]);
+  const [loading, setLoading] = useState(false);
+
+  // 加载评价列表
+  const loadEvaluations = async () => {
+    setLoading(true);
+    try {
+      const response = await axios.get('/secondTradeRecord/getSellerEvaluationList', {
+        params: { sellerId, pageNum, pageSize }
+      });
+      
+      const { records, total: totalCount } = response.data.data;
+      setEvaluations(records);
+      setTotal(totalCount);
+    } catch (error) {
+      console.error('加载失败:', error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    loadEvaluations();
+  }, [pageNum, pageSize, sellerId]);
+
+  // 页码改变
+  const handlePageChange = (page, size) => {
+    setPageNum(page);
+    setPageSize(size);
+  };
+
+  return (
+    <div>
+      <List
+        loading={loading}
+        dataSource={evaluations}
+        renderItem={(item) => (
+          <List.Item>
+            <List.Item.Meta
+              avatar={<Avatar src={item.buyerImage} />}
+              title={
+                <div>
+                  <span>{item.buyerName}</span>
+                  <Rate value={item.buyerRating} disabled style={{ marginLeft: 10 }} />
+                </div>
+              }
+              description={
+                <>
+                  <div>{item.buyerEvaluate}</div>
+                  <div style={{ color: '#999', fontSize: 12, marginTop: 5 }}>
+                    {item.createdTime}
+                  </div>
+                </>
+              }
+            />
+          </List.Item>
+        )}
+      />
+      
+      <Pagination
+        current={pageNum}
+        pageSize={pageSize}
+        total={total}
+        onChange={handlePageChange}
+        showSizeChanger
+        showQuickJumper
+        showTotal={(total) => `共 ${total} 条`}
+        pageSizeOptions={[10, 20, 50, 100]}
+      />
+    </div>
+  );
+}
+
+export default SellerEvaluationList;
+```
+
+---
+
+## 注意事项
+
+### ⚠️ 重要提示
+
+1. **必传参数**
+   - `sellerId` 为必传参数,用于查看指定用户的评价列表
+   - 不传或传空会返回错误:`"卖家ID不能为空"`
+
+2. **分页参数**
+   - `pageNum` 和 `pageSize` 为可选参数,有默认值
+   - pageSize 最大值限制为 100,超过自动修正
+
+3. **数据权限**
+   - 接口不做权限校验,任何人都可以查看用户主页的公开评价
+   - 如需增加权限控制,请联系后端开发人员
+
+4. **空数据处理**
+   - 如果该用户没有收到任何评价,返回空数组 `[]`
+   - total 为 0,pages 为 0
+   - 前端需要处理空状态展示
+
+5. **字段可空性**
+   - 部分字段可能为 `null`,前端需要做空值判断
+   - 建议使用可选链操作符或空值合并运算符
+
+---
+
+## 版本历史
+
+| 版本 | 日期 | 修改内容 | 修改人 |
+|------|------|----------|--------|
+| v1.0 | 2025-11-18 | 初始版本,返回全部数据 | 开发团队 |
+| v1.1 | 2025-11-18 | **新增分页功能** | 开发团队 |
+| v1.1 | 2025-11-18 | 优化查询方式,使用 MyBatis Plus | 开发团队 |
+| v1.1 | 2025-11-18 | 增加风控评分等级计算 | 开发团队 |
+| v1.1 | 2025-11-18 | 增加更多用户信息字段 | 开发团队 |
+
+---
+
+## 联系方式
+
+如有问题或建议,请联系:
+
+- **开发团队**:二手交易平台开发组
+- **服务名称**:alien-second
+- **文档路径**:`alien-second/doc/获取卖家交易评价列表接口文档.md`
+
+---
+
+*最后更新时间:2025-11-18(v1.1 - 支持分页)*

+ 471 - 0
alien-second/src/main/java/shop/alien/second/config/GaoDeMapUtil.java

@@ -0,0 +1,471 @@
+package shop.alien.second.config;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.store.EssentialCityCode;
+import shop.alien.mapper.EssentialCityCodeMapper;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 高德地图工具类
+ *
+ * @author ssk
+ * @version 1.0.0
+ * @discription 打死你个龟孙
+ * @since 1937-7-7
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class GaoDeMapUtil {
+
+    @Value("${gaode.key}")
+    private String key;
+
+    @Value("${gaode.geoUrl}")
+    private String geoUrl;
+
+    @Value("${gaode.geoListUrl}")
+    private String geoListUrl;
+
+    @Value("${gaode.getDistrict}")
+    private String getDistrict;
+
+    @Value("${gaode.distanceUrl}")
+    private String distanceUrl;
+
+    @Value("${gaode.distanceTypeUrl}")
+    private String distanceTypeUrl;
+
+    @Value("${gaode.nearUrl}")
+    private String nearUrl;
+
+    @Value("${gaode.subwayUrl}")
+    private String subwayUrl;
+
+    @Value("${gaode.addressUrl}")
+    private String addressUrl;
+
+    private final EssentialCityCodeMapper essentialCityCodeMapper;
+
+    /**
+     * 地址转经纬度
+     *
+     * @param address 地址
+     * @return 经纬度
+     */
+    public String getLonAndLatByAddress(String address) {
+        String formattedUrl = String.format(geoUrl, address, key);
+        JSONObject obj = getResponse(formattedUrl);
+        if (null != obj && "1".equals(String.valueOf(obj.get("status")))) {
+            JSONArray geocodesArray = obj.getJSONArray("geocodes");
+            if (geocodesArray != null && !geocodesArray.isEmpty()) {
+                JSONObject jobJSON = geocodesArray.getJSONObject(0);
+                return jobJSON.getString("location");
+            } else {
+                log.error("AddressLocationUtil.getLonAndLatByAddress ERROR 未找到与地址匹配的经纬度信息");
+                return "ERROR 未找到与地址匹配的经纬度信息";
+            }
+        } else {
+            log.error("AddressLocationUtil.getLonAndLatByAddress ERROR 地址转换经纬度失败");
+            return "ERROR 地址转换经纬度失败";
+        }
+    }
+
+    /**
+     * 地址转经纬度
+     *
+     * @param address 地址
+     * @return 经纬度
+     */
+    public JSONObject getInputPrompt(String address, String city) {
+        //如果按照城市查询
+        String cityCode = "";
+        if (StringUtils.isNotEmpty(city)) {
+            EssentialCityCode essentialCityCode = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaName, city));
+            if (null != essentialCityCode) {
+                cityCode = essentialCityCode.getCityCode().toString();
+            }
+        }
+        String formattedUrl = String.format(geoListUrl, address, key, cityCode);
+        JSONObject obj = getResponse(formattedUrl);
+        return obj;
+    }
+
+    /**
+     * 地址转经纬度
+     * cityCode:城市编码
+     *
+     * @return
+     */
+    public JSONObject getDistrict(String cityCode) {
+        String formattedUrl = String.format(getDistrict, key, cityCode);
+        JSONObject obj = getResponse(formattedUrl);
+        return obj;
+    }
+
+    /**
+     * 计算两个经纬度的距离
+     *
+     * @param longitudeOne 经度1
+     * @param latitudeOne  纬度1
+     * @param longitudeTwo 经度2
+     * @param latitudeTwo  纬度2
+     * @return distance
+     */
+    public Double getDistance(String longitudeOne, String latitudeOne, String longitudeTwo, String latitudeTwo) {
+        try {
+            String urlString = String.format(distanceUrl, key, longitudeOne, latitudeOne, longitudeTwo, latitudeTwo);
+            JSONObject obj = getResponse(urlString);
+            if (null != obj && "1".equals(String.valueOf(obj.get("status")))) {
+                JSONArray resultsArray = obj.getJSONArray("results");
+                if (resultsArray != null && !resultsArray.isEmpty()) {
+                    JSONObject resultJSON = resultsArray.getJSONObject(0);
+                    return Double.parseDouble(resultJSON.getString("distance"));
+                } else {
+                    log.error("AddressLocationUtil.getDistance ERROR 计算距离失败,结果为空");
+                    return null;
+                }
+            } else {
+                log.error("AddressLocationUtil.getDistance ERROR 计算距离失败");
+                return null;
+            }
+        } catch (Exception e) {
+            log.error("AddressLocationUtil.getDistance ERROR {}", e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 计算两个经纬度的距离
+     *
+     * @param longitudeOne 经度1
+     * @param latitudeOne  纬度1
+     * @param longitudeTwo 经度2
+     * @param latitudeTwo  纬度2
+     * @return distance
+     */
+    public Double getDistanceStraightLine(String longitudeOne, String latitudeOne, String longitudeTwo, String latitudeTwo) {
+        try {
+            String urlString = String.format(distanceTypeUrl, key, longitudeOne, latitudeOne, longitudeTwo, latitudeTwo, 0);
+            JSONObject obj = getResponse(urlString);
+            if (null != obj && "1".equals(String.valueOf(obj.get("status")))) {
+                JSONArray resultsArray = obj.getJSONArray("results");
+                if (resultsArray != null && !resultsArray.isEmpty()) {
+                    JSONObject resultJSON = resultsArray.getJSONObject(0);
+                    return Double.parseDouble(resultJSON.getString("distance"));
+                } else {
+                    log.error("AddressLocationUtil.getDistance ERROR 计算距离失败,结果为空");
+                    return null;
+                }
+            } else {
+                log.error("AddressLocationUtil.getDistance ERROR 计算距离失败");
+                return null;
+            }
+        } catch (Exception e) {
+            log.error("AddressLocationUtil.getDistance ERROR {}", e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 获取附近商家、建筑等信息
+     *
+     * @param longitude 经度
+     * @param latitude  纬度
+     * @return
+     */
+    public JSONArray getNear(String longitude, String latitude) {
+        try {
+            String urlString = String.format(nearUrl, longitude, latitude, key, 10000, 20, 1);
+            JSONObject obj = getResponse(urlString);
+            if (null != obj && "1".equals(String.valueOf(obj.get("status")))) {
+                JSONArray resultsArray = obj.getJSONArray("pois");
+                return resultsArray;
+            }
+        } catch (Exception e) {
+            log.error("AddressLocationUtil.getNear ERROR {}", e.getMessage());
+        }
+        return new JSONArray();
+    }
+
+    /**
+     * 获取10公里范围内的商场
+     *
+     * @param longitude 经度
+     * @param latitude  纬度
+     * @return 商场列表
+     */
+    public JSONArray getNearbyShoppingMalls(String longitude, String latitude) {
+        try {
+            // 使用高德地图POI搜索接口,搜索类型为商场(060100)
+            String urlString = String.format(nearUrl, longitude, latitude, key, 10000, 20, 1) + "&types=060100";
+            JSONObject obj = getResponse(urlString);
+            if (null != obj && "1".equals(String.valueOf(obj.get("status")))) {
+                JSONArray resultsArray = obj.getJSONArray("pois");
+                return resultsArray;
+            }
+        } catch (Exception e) {
+            log.error("GaoDeMapUtil.getNearbyShoppingMalls ERROR {}", e.getMessage());
+        }
+        return new JSONArray();
+    }
+
+    public JSONObject getNearbySubway(String longitude, String latitude) {
+        try {
+            // 使用高德地图POI搜索接口,搜索类型为地铁站(010100)
+            String encodedKeywords = URLEncoder.encode("地铁站", "UTF-8");
+            String urlString = String.format(
+                    subwayUrl,
+                    key, latitude, longitude, encodedKeywords, 1000, 150500);
+            JSONObject obj = getResponse(urlString);
+            if (null != obj && "1".equals(String.valueOf(obj.get("status")))) {
+                JSONObject pois = obj.getJSONArray("pois").getJSONObject(0);
+                return pois;
+            }
+        } catch (Exception e) {
+            log.error("GaoDeMapUtil.getNearbySubway ERROR {}", e.getMessage());
+        }
+        return new JSONObject();
+    }
+
+    /**
+     * 通过经纬度获取地址信息
+     *
+     * @param longitude
+     * @param latitude
+     * @param radius
+     * @param extensions
+     * @return
+     */
+    public Map<String, String> getAddressByLonAndLat(String longitude, String latitude, String radius, String extensions) {
+        String formattedUrl = String.format(addressUrl, longitude, latitude, key, radius, extensions);
+        JSONObject obj = getResponse(formattedUrl);
+        try {
+            if (null != obj && "1".equals(String.valueOf(obj.get("status")))) {
+                Map<String, String> address = new HashMap<>();
+                JSONObject regeocodeObject = obj.getJSONObject("regeocode");
+                JSONObject addressComponent = regeocodeObject.getJSONObject("addressComponent");
+                address.put("city", addressComponent.getString("city"));
+                address.put("district", addressComponent.getString("district"));
+                address.put("province", addressComponent.getString("province"));
+                JSONObject streetNumber = addressComponent.getJSONObject("streetNumber");
+                address.put("street", streetNumber.getString("street"));
+                address.put("streetNumber", streetNumber.getString("number"));
+                JSONObject pois = regeocodeObject.getJSONArray("pois").getJSONObject(0);
+                address.put("poiName", pois.getString("name"));
+                return address;
+            }
+        } catch (Exception e) {
+            log.error("GaoDeMapUtil.getAddressByLonAndLat ERROR {}", e.getMessage());
+        }
+        return new HashMap<>();
+    }
+
+    /**
+     * 根据经纬度获取当前城市与城市半径
+     *
+     * @param longitude 经度
+     * @param latitude  纬度
+     * @return Map包含城市名称、城市编码、城市中心点、城市半径(米)
+     */
+    public Map<String, Object> getCityAndRadiusByLonAndLat(String longitude, String latitude) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            // 1. 通过逆地理编码获取城市信息
+            String formattedUrl = String.format(addressUrl, longitude, latitude, key, "1000", "base");
+            JSONObject geoObj = getResponse(formattedUrl);
+            
+            if (null == geoObj || !"1".equals(String.valueOf(geoObj.get("status")))) {
+                log.error("GaoDeMapUtil.getCityAndRadiusByLonAndLat 逆地理编码失败");
+                return result;
+            }
+            
+            JSONObject regeocodeObject = geoObj.getJSONObject("regeocode");
+            JSONObject addressComponent = regeocodeObject.getJSONObject("addressComponent");
+            
+            String cityName = addressComponent.getString("city");
+            String adcode = addressComponent.getString("adcode");  // 区县级别代码
+            String province = addressComponent.getString("province");
+            
+            // 判断是否是直辖市
+            boolean isMunicipality = StringUtils.isEmpty(cityName) || "[]".equals(cityName);
+            if (isMunicipality) {
+                cityName = province;
+            }
+            
+            // 获取城市级别的行政区划代码
+            String cityCode = getCityLevelCode(adcode, isMunicipality, province);
+            
+            result.put("city", cityName);
+            result.put("province", province);
+            result.put("cityCode", cityCode);
+            
+            // 2. 通过行政区域查询获取城市边界信息
+            // 注意:必须添加 extensions=all 和 subdistrict=0 参数才能获取到polyline边界数据
+            String districtUrl = String.format(getDistrict, key, cityCode) + "&extensions=all&subdistrict=0";
+            JSONObject districtObj = getResponse(districtUrl);
+            
+            if (null != districtObj && "1".equals(String.valueOf(districtObj.get("status")))) {
+                JSONArray districts = districtObj.getJSONArray("districts");
+                if (districts != null && !districts.isEmpty()) {
+                    JSONObject district = districts.getJSONObject(0);
+                    String center = district.getString("center");
+                    result.put("center", center);
+                    
+                    // 3. 计算城市半径:获取边界坐标,计算中心点到边界的最大距离
+                    String polyline = district.getString("polyline");
+                    if (StringUtils.isNotEmpty(polyline) && !"|".equals(polyline)) {
+                        Double radius = calculateCityRadius(center, polyline);
+                        result.put("radius", radius);
+                        // 保留两位小数
+                        result.put("radiusKm", Math.round(radius / 10.0) / 100.0);
+                    } else {
+                        // 如果没有边界数据,返回默认半径
+                        result.put("radius", 50000.0);  // 默认50公里
+                        result.put("radiusKm", 50.00);
+                        log.warn("GaoDeMapUtil.getCityAndRadiusByLonAndLat 城市{}没有边界数据,使用默认半径", cityName);
+                    }
+                } else {
+                    log.error("GaoDeMapUtil.getCityAndRadiusByLonAndLat 未查询到城市行政区域信息");
+                }
+            } else {
+                log.error("GaoDeMapUtil.getCityAndRadiusByLonAndLat 行政区域查询失败");
+            }
+            
+        } catch (Exception e) {
+            log.error("GaoDeMapUtil.getCityAndRadiusByLonAndLat ERROR {}", e.getMessage(), e);
+        }
+        return result;
+    }
+
+    /**
+     * 获取城市级别的行政区划代码
+     * 
+     * @param adcode 区县级别的行政区划代码
+     * @param isMunicipality 是否是直辖市
+     * @param province 省份名称
+     * @return 城市级别的行政区划代码
+     */
+    private String getCityLevelCode(String adcode, boolean isMunicipality, String province) {
+        if (StringUtils.isEmpty(adcode) || adcode.length() != 6) {
+            return adcode;
+        }
+        
+        // 直辖市(北京、上海、天津、重庆):使用省级代码(前2位+0000)
+        if (isMunicipality || "北京市".equals(province) || "上海市".equals(province) 
+                || "天津市".equals(province) || "重庆市".equals(province)) {
+            return adcode.substring(0, 2) + "0000";
+        }
+        
+        // 普通地级市:使用市级代码(前4位+00)
+        return adcode.substring(0, 4) + "00";
+    }
+
+    /**
+     * 计算城市半径:计算中心点到边界的最大直线距离
+     *
+     * @param center   中心点坐标 "经度,纬度"
+     * @param polyline 边界坐标串 "经度,纬度;经度,纬度;..." 或 "经度,纬度;经度,纬度|经度,纬度;经度,纬度"
+     * @return 城市半径(米)
+     */
+    private Double calculateCityRadius(String center, String polyline) {
+        try {
+            String[] centerCoords = center.split(",");
+            double centerLon = Double.parseDouble(centerCoords[0]);
+            double centerLat = Double.parseDouble(centerCoords[1]);
+            
+            // 处理多个区域的情况(用|分隔)
+            String[] areas = polyline.split("\\|");
+            double maxDistance = 0.0;
+            
+            for (String area : areas) {
+                String[] points = area.split(";");
+                // 采样边界点,避免计算量过大(每隔10个点采样一次)
+                int step = Math.max(1, points.length / 50);
+                for (int i = 0; i < points.length; i += step) {
+                    String[] pointCoords = points[i].split(",");
+                    if (pointCoords.length >= 2) {
+                        double pointLon = Double.parseDouble(pointCoords[0]);
+                        double pointLat = Double.parseDouble(pointCoords[1]);
+                        
+                        // 使用本地Haversine公式计算直线距离(不调用API,速度快)
+                        double distance = haversineDistance(centerLon, centerLat, pointLon, pointLat);
+                        if (distance > maxDistance) {
+                            maxDistance = distance;
+                        }
+                    }
+                }
+            }
+            
+            return maxDistance;
+        } catch (Exception e) {
+            log.error("GaoDeMapUtil.calculateCityRadius ERROR {}", e.getMessage());
+            return 50000.0; // 默认50公里
+        }
+    }
+
+    /**
+     * 使用Haversine公式计算两点间的直线距离(本地计算,不调用API)
+     *
+     * @param lon1 起点经度
+     * @param lat1 起点纬度
+     * @param lon2 终点经度
+     * @param lat2 终点纬度
+     * @return 距离(米)
+     */
+    private double haversineDistance(double lon1, double lat1, double lon2, double lat2) {
+        final double EARTH_RADIUS = 6371000; // 地球半径(米)
+        
+        // 转换为弧度
+        double lat1Rad = Math.toRadians(lat1);
+        double lat2Rad = Math.toRadians(lat2);
+        double deltaLat = Math.toRadians(lat2 - lat1);
+        double deltaLon = Math.toRadians(lon2 - lon1);
+        
+        // Haversine公式
+        double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
+                   Math.cos(lat1Rad) * Math.cos(lat2Rad) *
+                   Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
+        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+        
+        return EARTH_RADIUS * c;
+    }
+
+    /**
+     * 创建请求
+     *
+     * @param serverUrl 请求地址
+     * @return JSONObject
+     */
+    private JSONObject getResponse(String serverUrl) {
+        StringBuilder result = new StringBuilder();
+        try {
+            URL url = new URL(serverUrl);
+            URLConnection conn = url.openConnection();
+            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+            String line;
+            while ((line = in.readLine()) != null) {
+                result.append(line);
+            }
+            in.close();
+        } catch (Exception e) {
+            log.error("AddressLocationUtil.getResponse ERROR {}", e.getMessage());
+            return null;
+        }
+        return JSONObject.parseObject(result.toString());
+    }
+}

+ 206 - 0
alien-second/src/main/java/shop/alien/second/controller/SecondEntrustUserController.java

@@ -0,0 +1,206 @@
+package shop.alien.second.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.second.SecondEntrustUser;
+import shop.alien.entity.second.vo.SecondEntrustUserDTO;
+import shop.alien.entity.second.vo.SecondEntrustUserDetailVo;
+import shop.alien.entity.second.vo.SecondEntrustUserQueryVo;
+import shop.alien.entity.second.vo.SecondEntrustUserResultVo;
+import shop.alien.second.service.SecondEntrustUserService;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 二手委托人信息表 前端控制器
+ * </p>
+ *
+ * @author ssk
+ * @since 2025-11-21
+ */
+@Slf4j
+@Api(tags = {"二手平台-委托人信息管理"})
+@ApiSort(10)
+@CrossOrigin
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/secondEntrustUser")
+public class SecondEntrustUserController {
+
+    private final SecondEntrustUserService secondEntrustUserService;
+
+    @ApiOperation("创建委托人信息")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/create")
+    public R<Boolean> createEntrustUser(@Validated @RequestBody SecondEntrustUserDTO dto) {
+        log.info("SecondEntrustUserController.createEntrustUser dto={}", dto);
+        try {
+            boolean result = secondEntrustUserService.createEntrustUser(dto);
+            if (result) {
+                return R.success("创建成功");
+            }
+            return R.fail("创建失败");
+        } catch (Exception e) {
+            log.error("SecondEntrustUserController.createEntrustUser error: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据交易ID获取委托人信息")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "entrustTradeId", value = "交易ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getByTradeId")
+    public R<SecondEntrustUser> getByTradeId(@RequestParam Integer entrustTradeId) {
+        log.info("SecondEntrustUserController.getByTradeId entrustTradeId={}", entrustTradeId);
+        try {
+            SecondEntrustUser entrustUser = secondEntrustUserService.getByTradeId(entrustTradeId);
+            return R.data(entrustUser);
+        } catch (Exception e) {
+            log.error("SecondEntrustUserController.getByTradeId error: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据交易编号获取委托人信息")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "entrustTradeNo", value = "交易编号", dataType = "String", paramType = "query", required = true)
+    })
+    @GetMapping("/getByTradeNo")
+    public R<SecondEntrustUser> getByTradeNo(@RequestParam String entrustTradeNo) {
+        log.info("SecondEntrustUserController.getByTradeNo entrustTradeNo={}", entrustTradeNo);
+        try {
+            SecondEntrustUser entrustUser = secondEntrustUserService.getByTradeNo(entrustTradeNo);
+            return R.data(entrustUser);
+        } catch (Exception e) {
+            log.error("SecondEntrustUserController.getByTradeNo error: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据ID获取委托人信息")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "委托人ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getById")
+    public R<SecondEntrustUser> getById(@RequestParam Integer id) {
+        log.info("SecondEntrustUserController.getById id={}", id);
+        try {
+            SecondEntrustUser entrustUser = secondEntrustUserService.getById(id);
+            if (entrustUser == null) {
+                return R.fail("委托人信息不存在");
+            }
+            return R.data(entrustUser);
+        } catch (Exception e) {
+            log.error("SecondEntrustUserController.getById error: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("更新委托人信息")
+    @ApiOperationSupport(order = 5)
+    @PutMapping("/update/{id}")
+    public R<Boolean> updateEntrustUser(
+            @ApiParam(value = "委托人ID", required = true) @PathVariable Integer id,
+            @Validated @RequestBody SecondEntrustUserDTO dto) {
+        log.info("SecondEntrustUserController.updateEntrustUser id={}, dto={}", id, dto);
+        try {
+            boolean result = secondEntrustUserService.updateEntrustUser(id, dto);
+            if (result) {
+                return R.success("更新成功");
+            }
+            return R.fail("更新失败");
+        } catch (Exception e) {
+            log.error("SecondEntrustUserController.updateEntrustUser error: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("删除委托人信息")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "委托人ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @DeleteMapping("/delete")
+    public R<Boolean> deleteEntrustUser(@RequestParam Integer id) {
+        log.info("SecondEntrustUserController.deleteEntrustUser id={}", id);
+        try {
+            boolean result = secondEntrustUserService.deleteEntrustUser(id);
+            if (result) {
+                return R.success("删除成功");
+            }
+            return R.fail("删除失败");
+        } catch (Exception e) {
+            log.error("SecondEntrustUserController.deleteEntrustUser error: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据用户电话查询委托人信息列表")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "entrustUserPhone", value = "用户电话", dataType = "String", paramType = "query", required = true)
+    })
+    @GetMapping("/getByUserPhone")
+    public R<List<SecondEntrustUser>> getByUserPhone(@RequestParam String entrustUserPhone) {
+        log.info("SecondEntrustUserController.getByUserPhone entrustUserPhone={}", entrustUserPhone);
+        try {
+            List<SecondEntrustUser> list = secondEntrustUserService.getByUserPhone(entrustUserPhone);
+            return R.data(list);
+        } catch (Exception e) {
+            log.error("SecondEntrustUserController.getByUserPhone error: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("分页查询委托人信息列表")
+    @ApiOperationSupport(order = 7)
+    @PostMapping("/page")
+    public R<IPage<SecondEntrustUserResultVo>> getEntrustUserPage(@RequestBody SecondEntrustUserQueryVo queryVo) {
+        log.info("SecondEntrustUserController.getEntrustUserPage queryVo={}", queryVo);
+        try {
+            // 设置默认分页参数
+            if (queryVo.getPageNum() == null || queryVo.getPageNum() < 1) {
+                queryVo.setPageNum(1);
+            }
+            if (queryVo.getPageSize() == null || queryVo.getPageSize() < 1) {
+                queryVo.setPageSize(10);
+            }
+            
+            IPage<SecondEntrustUserResultVo> page = secondEntrustUserService.getEntrustUserPage(queryVo);
+            return R.data(page);
+        } catch (Exception e) {
+            log.error("SecondEntrustUserController.getEntrustUserPage error: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据姓名和身份证号获取委托人详情")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "entrustUserName", value = "委托人姓名", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "entrustIdCard", value = "委托人身份证号", dataType = "String", paramType = "query", required = true)
+    })
+    @GetMapping("/detail")
+    public R<SecondEntrustUserDetailVo> getEntrustUserDetail(@RequestParam String entrustUserName, @RequestParam String entrustIdCard) {
+        log.info("SecondEntrustUserController.getEntrustUserDetail entrustUserName={}, entrustIdCard={}", entrustUserName, entrustIdCard);
+        try {
+            SecondEntrustUserDetailVo detailVo = secondEntrustUserService.getEntrustUserDetail(entrustUserName, entrustIdCard);
+            return R.data(detailVo);
+        } catch (Exception e) {
+            log.error("SecondEntrustUserController.getEntrustUserDetail error: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+}
+

+ 33 - 0
alien-second/src/main/java/shop/alien/second/controller/SecondGaoDeController.java

@@ -0,0 +1,33 @@
+package shop.alien.second.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiSort;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.second.config.GaoDeMapUtil;
+
+import java.util.Map;
+
+@Slf4j
+@Api(tags = {"二手平台-高德"})
+@ApiSort(9)
+@CrossOrigin
+@RestController
+@RequestMapping("/second/gaode")
+@RequiredArgsConstructor
+public class SecondGaoDeController {
+
+    private final GaoDeMapUtil gaodeMapUtil;
+
+    @ApiOperation("获取城市与城市半径")
+    @GetMapping("/queryCity")
+    public R<Map<String, Object>> queryCity(
+            @RequestParam(value = "longitude", required = false) String longitude,
+            @RequestParam(value = "latitude", required = false) String latitude) {
+        return R.data(gaodeMapUtil.getCityAndRadiusByLonAndLat(longitude, latitude), "查询成功");
+    }
+
+}

+ 6 - 4
alien-second/src/main/java/shop/alien/second/controller/SecondRecommendController.java

@@ -30,10 +30,11 @@ public class SecondRecommendController {
             @RequestParam(value = "pageSize", required = false) Integer pageSize,
             @RequestParam(value = "typeId", required = false) Integer typeId,
             @RequestParam(value = "longitude", required = false) String longitude,
-            @RequestParam(value = "latitude", required = false) String latitude) throws Exception {
+            @RequestParam(value = "latitude", required = false) String latitude,
+            @RequestParam(value = "radiusKm", required = false) String radiusKm) throws Exception {
         log.info("LifeCollectController.cancelCollect?pageNum={},pageSize={},longitude={},latitude={},typeId={}", pageNum, pageSize, longitude, latitude, typeId);
         IPage<SecondGoodsRecommendVo> page = new Page<>(pageNum, pageSize);
-        IPage<SecondGoodsRecommendVo> result = service.getSecondRecommendByPage(page, longitude, latitude, typeId);
+        IPage<SecondGoodsRecommendVo> result = service.getSecondRecommendByPage(page, longitude, latitude, typeId, radiusKm);
         return R.data(result, "查询成功");
     }
 
@@ -56,10 +57,11 @@ public class SecondRecommendController {
             @RequestParam(value = "pageNum", required = false) Integer pageNum,
             @RequestParam(value = "pageSize", required = false) Integer pageSize,
             @RequestParam(value = "longitude", required = false) String longitude,
-            @RequestParam(value = "latitude", required = false) String latitude) throws Exception {
+            @RequestParam(value = "latitude", required = false) String latitude,
+            @RequestParam(value = "radiusKm", required = false) String radiusKm) throws Exception {
         log.info("LifeCollectController.cancelCollect?pageNum={},pageSize={},longitude={},latitude={}", pageNum, pageSize, longitude, latitude);
         IPage<SecondGoodsRecommendVo> page = new Page<>(pageNum, pageSize);
-        IPage<SecondGoodsRecommendVo> result = service.querySecondNewGoodsByPage(page, longitude + "," + latitude);
+        IPage<SecondGoodsRecommendVo> result = service.querySecondNewGoodsByPage(page, longitude + "," + latitude, radiusKm);
         return R.data(result, "查询成功");
     }
 

+ 68 - 9
alien-second/src/main/java/shop/alien/second/controller/SecondTradeRecordController.java

@@ -1,7 +1,7 @@
 package shop.alien.second.controller;
 
 
-import com.alipay.api.kms.aliyun.utils.BackoffUtils;
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -9,7 +9,9 @@ import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.BusinessException;
 import shop.alien.entity.result.R;
 import shop.alien.entity.second.SecondTradeRecord;
+import shop.alien.entity.second.vo.SecondEntrustUserDTO;
 import shop.alien.entity.second.vo.SecondTradeRecordVo;
+import shop.alien.entity.second.vo.SellerEvaluationVo;
 import shop.alien.second.service.SecondTradeRecordService;
 
 import java.util.List;
@@ -36,7 +38,7 @@ public class SecondTradeRecordController {
     @ApiOperation("创建交易")
     @ApiOperationSupport(order = 1)
     @PostMapping("/createTrade")
-    public R<Boolean> createTrade(@RequestBody SecondTradeRecord entity) throws Exception {
+    public R<Boolean> createTrade(@RequestBody SecondEntrustUserDTO entity) throws Exception {
         log.info("SecondTradeRecordController.createTrade?entity={}", entity.toString());
         if (secondTradeRecordService.createTrade(entity)) return R.success("创建成功");
         return R.fail("创建失败");
@@ -117,11 +119,12 @@ public class SecondTradeRecordController {
     @ApiImplicitParams({
             @ApiImplicitParam(name = "tradeId", value = "交易id", dataType = "Integer", paramType = "query", required = true),
             @ApiImplicitParam(name = "type", value = "1-交易成功  2-交易失败", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "evaluate", value = "评价", dataType = "String", paramType = "query")})
+            @ApiImplicitParam(name = "evaluate", value = "评价", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "rating", value = "评分", dataType = "Integer", paramType = "query")})
     @GetMapping("/tradeCompleteConfirm")
-    public R<Boolean> tradeCompleteConfirm(@RequestParam int tradeId, @RequestParam int type, String evaluate) throws Exception {
-        log.info("SecondTradeRecordController.tradeCompleteConfirm?tradeId={}, type={}, evaluate={}", tradeId, type, evaluate);
-        return R.data(secondTradeRecordService.tradeCompleteConfirm(tradeId, type, evaluate));
+    public R<Boolean> tradeCompleteConfirm(@RequestParam int tradeId, @RequestParam int type, String evaluate, Integer rating) throws Exception {
+        log.info("SecondTradeRecordController.tradeCompleteConfirm?tradeId={}, type={}, evaluate={}, rating={}", tradeId, type, evaluate, rating);
+        return R.data(secondTradeRecordService.tradeCompleteConfirm(tradeId, type, evaluate, rating));
     }
 
     @ApiOperation("获取用户交易确认信息")
@@ -153,10 +156,66 @@ public class SecondTradeRecordController {
 
     @ApiOperation("修改交易信息")
     @ApiOperationSupport(order = 11)
-    @ApiImplicitParams({@ApiImplicitParam(name = "type", value = "1-交易成功  2-交易失败", dataType = "Integer", paramType = "query", required = true)})
+    @ApiImplicitParams({@ApiImplicitParam(name = "type", value = "1-确认  2-拒绝", dataType = "Integer", paramType = "query", required = true)})
     @GetMapping("/modifyTradeRecord")
-    public R<Boolean> modifyTradeRecord(int type, Integer tradeId, String transactionTime, String transactionLatitudeLongitude, String transactionLatitudeLongitudeAddress, String transactionLocation, String messageId) throws Exception {
+    public R<Boolean> modifyTradeRecord(int type, Integer tradeId, String transactionTime,
+                                        String transactionLatitudeLongitude,
+                                        String transactionLatitudeLongitudeAddress,
+                                        String transactionLocation,
+                                        String messageId, String entrustUserPhone, String entrustUserName, String entrustIdCard, Integer entrustId) throws Exception {
         log.info("SecondTradeRecordController.modifyTradeRecord?record={}", transactionTime);
-        return R.data(secondTradeRecordService.modifyTradeRecord(type, tradeId, transactionTime, transactionLatitudeLongitude, transactionLatitudeLongitudeAddress, transactionLocation, messageId));
+        return R.data(secondTradeRecordService.modifyTradeRecord(type, tradeId, transactionTime, transactionLatitudeLongitude, transactionLatitudeLongitudeAddress, transactionLocation, messageId, entrustUserPhone, entrustUserName, entrustIdCard, entrustId));
+    }
+
+    @ApiOperation("获取用户作为卖家的交易评价列表(分页)")
+    @ApiOperationSupport(order = 12)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "sellerId", value = "卖家id(用户主页展示用,必传)", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "Integer", paramType = "query", required = false, defaultValue = "1"),
+            @ApiImplicitParam(name = "pageSize", value = "每页条数", dataType = "Integer", paramType = "query", required = false, defaultValue = "10")
+    })
+    @GetMapping("/getSellerEvaluationList")
+    public R<IPage<SellerEvaluationVo>> getSellerEvaluationList(
+            @RequestParam(required = true) Integer sellerId,
+            @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+            @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        log.info("SecondTradeRecordController.getSellerEvaluationList?sellerId={}, pageNum={}, pageSize={}", 
+                sellerId, pageNum, pageSize);
+        try {
+            IPage<SellerEvaluationVo> evaluationPage = secondTradeRecordService.getSellerEvaluationList(sellerId, pageNum, pageSize);
+            return R.data(evaluationPage);
+        } catch (Exception e) {
+            log.error("SecondTradeRecordController.getSellerEvaluationList?error: {}", e.getMessage(), e);
+            return R.fail("获取评价列表失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("加入定位")
+    @ApiOperationSupport(order = 13)
+    @ApiImplicitParams({@ApiImplicitParam(name = "otherUserId", value = "对方userId", dataType = "Integer", paramType = "query", required = true)})
+    @GetMapping("/locationShareAdd")
+    public R<String> locationShareAdd(Integer otherUserId) throws Exception {
+        log.info("SecondTradeRecordController.locationShareAdd?otherUserId={}", otherUserId);
+        secondTradeRecordService.locationShareAdd(otherUserId);
+        return R.data("操作成功");
+    }
+
+    @ApiOperation("退出定位")
+    @ApiOperationSupport(order = 14)
+    @ApiImplicitParams({@ApiImplicitParam(name = "otherUserId", value = "对方userId", dataType = "Integer", paramType = "query", required = true)})
+    @GetMapping("/locationShareDel")
+    public R<String> locationShareDel(Integer otherUserId) throws Exception {
+        log.info("SecondTradeRecordController.locationShareDel?otherUserId={}", otherUserId);
+        secondTradeRecordService.locationShareDel(otherUserId);
+        return R.success("操作成功");
+    }
+
+    @ApiOperation("是否在定位里面")
+    @ApiOperationSupport(order = 15)
+    @ApiImplicitParams({@ApiImplicitParam(name = "otherUserId", value = "对方userId", dataType = "Integer", paramType = "query", required = true)})
+    @GetMapping("/locationShareHas")
+    public R<Boolean> locationShareHas(Integer otherUserId) throws Exception {
+        log.info("SecondTradeRecordController.locationShareHas?otherUserId={}", otherUserId);
+        return R.data(secondTradeRecordService.locationShareHas(otherUserId));
     }
 }

+ 90 - 0
alien-second/src/main/java/shop/alien/second/service/SecondEntrustUserService.java

@@ -0,0 +1,90 @@
+package shop.alien.second.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.second.SecondEntrustUser;
+import shop.alien.entity.second.vo.SecondEntrustUserDTO;
+import shop.alien.entity.second.vo.SecondEntrustUserDetailVo;
+import shop.alien.entity.second.vo.SecondEntrustUserQueryVo;
+import shop.alien.entity.second.vo.SecondEntrustUserResultVo;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 二手委托人信息表 服务类
+ * </p>
+ *
+ * @author ssk
+ * @since 2025-11-21
+ */
+public interface SecondEntrustUserService extends IService<SecondEntrustUser> {
+
+    /**
+     * 创建委托人信息
+     *
+     * @param dto 委托人信息DTO
+     * @return 是否创建成功
+     */
+    boolean createEntrustUser(SecondEntrustUserDTO dto);
+
+    /**
+     * 根据交易ID获取委托人信息
+     *
+     * @param entrustTradeId 交易ID
+     * @return 委托人信息
+     */
+    SecondEntrustUser getByTradeId(Integer entrustTradeId);
+
+    /**
+     * 根据交易编号获取委托人信息
+     *
+     * @param entrustTradeNo 交易编号
+     * @return 委托人信息
+     */
+    SecondEntrustUser getByTradeNo(String entrustTradeNo);
+
+    /**
+     * 更新委托人信息
+     *
+     * @param id 委托人ID
+     * @param dto 委托人信息DTO
+     * @return 是否更新成功
+     */
+    boolean updateEntrustUser(Integer id, SecondEntrustUserDTO dto);
+
+    /**
+     * 删除委托人信息
+     *
+     * @param id 委托人ID
+     * @return 是否删除成功
+     */
+    boolean deleteEntrustUser(Integer id);
+
+    /**
+     * 根据用户电话查询委托人信息列表
+     *
+     * @param entrustUserPhone 用户电话
+     * @return 委托人信息列表
+     */
+    List<SecondEntrustUser> getByUserPhone(String entrustUserPhone);
+
+    /**
+     * 分页查询委托人信息列表(以姓名+身份证号为维度)
+     *
+     * @param queryVo 查询条件
+     * @return 委托人信息分页列表
+     */
+    IPage<SecondEntrustUserResultVo> getEntrustUserPage(SecondEntrustUserQueryVo queryVo);
+
+    /**
+     * 根据姓名和身份证号获取委托人详情(包含该人所有委托记录的交易信息)
+     *
+     * @param entrustUserName 委托人姓名
+     * @param entrustIdCard 委托人身份证号
+     * @return 委托人详情
+     */
+    SecondEntrustUserDetailVo getEntrustUserDetail(String entrustUserName, String entrustIdCard) throws Exception;
+
+}
+

+ 2 - 2
alien-second/src/main/java/shop/alien/second/service/SecondRecommendService.java

@@ -18,7 +18,7 @@ public interface SecondRecommendService extends IService<SecondGoodsRecommendVo>
      * @param typeId 类型ID
      * @return 推荐商品信息
      */
-    IPage<SecondGoodsRecommendVo> getSecondRecommendByPage(IPage<SecondGoodsRecommendVo> page, String longitude, String latitude, Integer typeId) throws Exception;
+    IPage<SecondGoodsRecommendVo> getSecondRecommendByPage(IPage<SecondGoodsRecommendVo> page, String longitude, String latitude, Integer typeId, String radiusKm) throws Exception;
 
     /**
      * 查询关注信息
@@ -34,7 +34,7 @@ public interface SecondRecommendService extends IService<SecondGoodsRecommendVo>
      * @param position 经纬度
      * @return 新品商品信息
      */
-    IPage<SecondGoodsRecommendVo> querySecondNewGoodsByPage(IPage<SecondGoodsRecommendVo> page, String position) throws Exception;
+    IPage<SecondGoodsRecommendVo> querySecondNewGoodsByPage(IPage<SecondGoodsRecommendVo> page, String position, String radiusKm) throws Exception;
 
     /**
      * 查询商品详情信息

+ 23 - 3
alien-second/src/main/java/shop/alien/second/service/SecondTradeRecordService.java

@@ -1,10 +1,13 @@
 package shop.alien.second.service;
 
 
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.result.BusinessException;
 import shop.alien.entity.second.SecondTradeRecord;
+import shop.alien.entity.second.vo.SecondEntrustUserDTO;
 import shop.alien.entity.second.vo.SecondTradeRecordVo;
+import shop.alien.entity.second.vo.SellerEvaluationVo;
 
 import java.util.List;
 
@@ -17,7 +20,7 @@ import java.util.List;
  * @since 2025-07-07
  */
 public interface SecondTradeRecordService extends IService<SecondTradeRecord> {
-    boolean createTrade(SecondTradeRecord entity) throws Exception;
+    boolean createTrade(SecondEntrustUserDTO entity) throws Exception;
 
     boolean goodsTradeConfirm(int goodsId) throws Exception;
 
@@ -31,11 +34,28 @@ public interface SecondTradeRecordService extends IService<SecondTradeRecord> {
 
     SecondTradeRecordVo getUserTradeStatus(int tradeId) throws Exception;
 
-    boolean tradeCompleteConfirm(int tradeId, int type, String evaluate) throws Exception;
+    boolean tradeCompleteConfirm(int tradeId, int type, String evaluate, Integer rating) throws Exception;
 
     List<SecondTradeRecordVo> getTradeRecord(int sideId) throws Exception;
 
     SecondTradeRecord hasInTradeRecord(Integer sideId) throws Exception;
 
-    boolean modifyTradeRecord(int type, Integer tradeId, String transactionTime, String transactionLatitudeLongitude, String transactionLatitudeLongitudeAddress, String transactionLocation, String messageId) throws Exception;
+    boolean modifyTradeRecord(int type, Integer tradeId, String transactionTime, String transactionLatitudeLongitude, String transactionLatitudeLongitudeAddress, String transactionLocation, String messageId,
+                              String userPhone, String userName, String idCard, Integer entrustId) throws Exception;
+
+    /**
+     * 获取用户作为卖家的交易评价列表(分页)
+     *
+     * @param sellerId 卖家ID(用户ID,必传)
+     * @param pageNum 页码
+     * @param pageSize 每页条数
+     * @return 卖家评价分页列表,包含买家信息、评价内容、评分、风控评分等级等
+     */
+    IPage<SellerEvaluationVo> getSellerEvaluationList(Integer sellerId, Integer pageNum, Integer pageSize) throws Exception;
+
+    void locationShareAdd(Integer otherUserId) throws Exception;
+
+    void locationShareDel(Integer otherUserId) throws Exception;
+
+    boolean locationShareHas(Integer otherUserId) throws Exception;
 }

+ 296 - 0
alien-second/src/main/java/shop/alien/second/service/impl/SecondEntrustUserServiceImpl.java

@@ -0,0 +1,296 @@
+package shop.alien.second.service.impl;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+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.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.second.SecondEntrustUser;
+import shop.alien.entity.second.SecondTradeRecord;
+import shop.alien.entity.second.vo.SecondEntrustUserDTO;
+import shop.alien.entity.second.vo.SecondEntrustUserDetailVo;
+import shop.alien.entity.second.vo.SecondEntrustUserQueryVo;
+import shop.alien.entity.second.vo.SecondEntrustUserResultVo;
+import shop.alien.entity.second.vo.SecondTradeRecordVo;
+import shop.alien.mapper.second.SecondEntrustUserMapper;
+import shop.alien.mapper.second.SecondTradeRecordMapper;
+import shop.alien.second.service.PlatformSecondTradeService;
+import shop.alien.second.service.SecondEntrustUserService;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * 二手委托人信息表 服务实现类
+ * </p>
+ *
+ * @author ssk
+ * @since 2025-11-21
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class SecondEntrustUserServiceImpl extends ServiceImpl<SecondEntrustUserMapper, SecondEntrustUser> implements SecondEntrustUserService {
+
+    private final SecondEntrustUserMapper secondEntrustUserMapper;
+    private final SecondTradeRecordMapper secondTradeRecordMapper;
+    private final PlatformSecondTradeService platformSecondTradeService;
+
+    /**
+     * 创建委托人信息
+     *
+     * @param dto 委托人信息DTO
+     * @return 是否创建成功
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean createEntrustUser(SecondEntrustUserDTO dto) {
+        log.info("SecondEntrustUserServiceImpl.createEntrustUser dto={}", dto);
+        try {
+            // 检查该交易是否已存在委托人信息
+            SecondEntrustUser existUser = getByTradeId(dto.getEntrustTradeId());
+            if (existUser != null) {
+                log.warn("交易ID:{}已存在委托人信息", dto.getEntrustTradeId());
+                throw new RuntimeException("该交易已存在委托人信息");
+            }
+
+            // 创建委托人信息
+            SecondEntrustUser entrustUser = new SecondEntrustUser();
+            BeanUtils.copyProperties(dto, entrustUser);
+            
+            return this.save(entrustUser);
+        } catch (Exception e) {
+            log.error("SecondEntrustUserServiceImpl.createEntrustUser error: {}", e.getMessage(), e);
+            throw new RuntimeException("创建委托人信息失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 根据交易ID获取委托人信息
+     *
+     * @param entrustTradeId 交易ID
+     * @return 委托人信息
+     */
+    @Override
+    public SecondEntrustUser getByTradeId(Integer entrustTradeId) {
+        log.info("SecondEntrustUserServiceImpl.getByTradeId entrustTradeId={}", entrustTradeId);
+        LambdaQueryWrapper<SecondEntrustUser> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(SecondEntrustUser::getEntrustTradeId, entrustTradeId);
+        return this.getOne(wrapper);
+    }
+
+    /**
+     * 根据交易编号获取委托人信息
+     *
+     * @param entrustTradeNo 交易编号
+     * @return 委托人信息
+     */
+    @Override
+    public SecondEntrustUser getByTradeNo(String entrustTradeNo) {
+        log.info("SecondEntrustUserServiceImpl.getByTradeNo entrustTradeNo={}", entrustTradeNo);
+        if (StringUtils.isBlank(entrustTradeNo)) {
+            return null;
+        }
+        LambdaQueryWrapper<SecondEntrustUser> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(SecondEntrustUser::getEntrustTradeNo, entrustTradeNo);
+        return this.getOne(wrapper);
+    }
+
+    /**
+     * 更新委托人信息
+     *
+     * @param id 委托人ID
+     * @param dto 委托人信息DTO
+     * @return 是否更新成功
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateEntrustUser(Integer id, SecondEntrustUserDTO dto) {
+        log.info("SecondEntrustUserServiceImpl.updateEntrustUser id={}, dto={}", id, dto);
+        try {
+            // 检查委托人信息是否存在
+            SecondEntrustUser entrustUser = this.getById(id);
+            if (entrustUser == null) {
+                log.warn("委托人信息不存在, id={}", id);
+                throw new RuntimeException("委托人信息不存在");
+            }
+
+            // 更新委托人信息
+            BeanUtils.copyProperties(dto, entrustUser);
+            entrustUser.setId(id);
+            
+            return this.updateById(entrustUser);
+        } catch (Exception e) {
+            log.error("SecondEntrustUserServiceImpl.updateEntrustUser error: {}", e.getMessage(), e);
+            throw new RuntimeException("更新委托人信息失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 删除委托人信息(逻辑删除)
+     *
+     * @param id 委托人ID
+     * @return 是否删除成功
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteEntrustUser(Integer id) {
+        log.info("SecondEntrustUserServiceImpl.deleteEntrustUser id={}", id);
+        try {
+            SecondEntrustUser entrustUser = this.getById(id);
+            if (entrustUser == null) {
+                log.warn("委托人信息不存在, id={}", id);
+                throw new RuntimeException("委托人信息不存在");
+            }
+            return this.removeById(id);
+        } catch (Exception e) {
+            log.error("SecondEntrustUserServiceImpl.deleteEntrustUser error: {}", e.getMessage(), e);
+            throw new RuntimeException("删除委托人信息失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 根据用户电话查询委托人信息列表
+     *
+     * @param entrustUserPhone 用户电话
+     * @return 委托人信息列表
+     */
+    @Override
+    public List<SecondEntrustUser> getByUserPhone(String entrustUserPhone) {
+        log.info("SecondEntrustUserServiceImpl.getByUserPhone entrustUserPhone={}", entrustUserPhone);
+        LambdaQueryWrapper<SecondEntrustUser> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(SecondEntrustUser::getEntrustUserPhone, entrustUserPhone);
+        wrapper.orderByDesc(SecondEntrustUser::getCreatedTime);
+        return this.list(wrapper);
+    }
+
+    /**
+     * 分页查询委托人信息列表(以姓名+身份证号为维度)
+     *
+     * @param queryVo 查询条件
+     * @return 委托人信息分页列表
+     */
+    @Override
+    public IPage<SecondEntrustUserResultVo> getEntrustUserPage(SecondEntrustUserQueryVo queryVo) {
+        log.info("SecondEntrustUserServiceImpl.getEntrustUserPage queryVo={}", queryVo);
+        try {
+            // 创建分页对象
+            Page<SecondEntrustUser> page = new Page<>(queryVo.getPageNum(), queryVo.getPageSize());
+            
+            // 构建查询条件(用于HAVING子句)
+            QueryWrapper<SecondEntrustUser> wrapper = new QueryWrapper<>();
+            
+            // 委托人姓名模糊查询
+            if (StringUtils.isNotBlank(queryVo.getEntrustUserName())) {
+                wrapper.apply("entrust.entrust_user_name LIKE CONCAT('%', {0}, '%')", queryVo.getEntrustUserName());
+            }
+            
+            // 身份证号模糊查询
+            if (StringUtils.isNotBlank(queryVo.getEntrustIdCard())) {
+                wrapper.apply("entrust.entrust_id_card LIKE CONCAT('%', {0}, '%')", queryVo.getEntrustIdCard());
+            }
+            
+            // 委托人电话模糊查询
+            if (StringUtils.isNotBlank(queryVo.getEntrustUserPhone())) {
+                wrapper.apply("entrust.entrust_user_phone LIKE CONCAT('%', {0}, '%')", queryVo.getEntrustUserPhone());
+            }
+            
+            // 交易编号模糊查询
+            if (StringUtils.isNotBlank(queryVo.getEntrustTradeNo())) {
+                wrapper.apply("trade.trade_no LIKE CONCAT('%', {0}, '%')", queryVo.getEntrustTradeNo());
+            }
+            // deleteFlag = 0
+            wrapper.eq("entrust.delete_flag", 0);
+            
+            // 执行分页查询
+            return secondEntrustUserMapper.getEntrustUserPage(page, wrapper);
+        } catch (Exception e) {
+            log.error("SecondEntrustUserServiceImpl.getEntrustUserPage error: {}", e.getMessage(), e);
+            throw new RuntimeException("查询委托人信息列表失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 根据姓名和身份证号获取委托人详情(包含该人所有委托记录的交易信息)
+     *
+     * @param entrustUserName 委托人姓名
+     * @param entrustIdCard 委托人身份证号
+     * @return 委托人详情
+     */
+    @Override
+    public SecondEntrustUserDetailVo getEntrustUserDetail(String entrustUserName, String entrustIdCard) throws Exception {
+        log.info("SecondEntrustUserServiceImpl.getEntrustUserDetail entrustUserName={}, entrustIdCard={}", entrustUserName, entrustIdCard);
+        try {
+            // 1. 根据姓名和身份证号查询该人的所有委托记录
+            List<SecondEntrustUser> entrustUsers = secondEntrustUserMapper.getByUserNameAndIdCard(entrustUserName, entrustIdCard);
+            
+            if (entrustUsers == null || entrustUsers.isEmpty()) {
+                log.warn("委托人信息不存在, entrustUserName={}, entrustIdCard={}", entrustUserName, entrustIdCard);
+                throw new RuntimeException("委托人信息不存在");
+            }
+            
+            // 2. 创建返回对象,使用第一条记录作为基本信息(同一个人的基本信息相同)
+            SecondEntrustUserDetailVo detailVo = new SecondEntrustUserDetailVo();
+            SecondEntrustUser firstRecord = entrustUsers.get(0);
+            detailVo.setEntrustUserInfo(firstRecord);
+            
+            // 3. 收集该人所有委托记录关联的交易ID
+            List<Integer> tradeIds = new ArrayList<>();
+            for (SecondEntrustUser entrustUser : entrustUsers) {
+                if (entrustUser.getEntrustTradeId() != null) {
+                    tradeIds.add(entrustUser.getEntrustTradeId());
+                }
+            }
+            
+            // 4. 查询所有关联的交易记录
+            List<SecondTradeRecordVo> tradeRecordVos = new ArrayList<>();
+            
+            if (!tradeIds.isEmpty()) {
+                // 去重
+                tradeIds = tradeIds.stream().distinct().collect(java.util.stream.Collectors.toList());
+                
+                // 批量查询交易记录
+                QueryWrapper<SecondTradeRecord> tradeWrapper = new QueryWrapper<>();
+                tradeWrapper.in("id", tradeIds)
+                        .eq("delete_flag", 0)
+                        .orderByDesc("created_time");
+                
+                List<SecondTradeRecord> tradeRecords = secondTradeRecordMapper.selectList(tradeWrapper);
+                
+                // 5. 处理每条交易记录,添加操作节点信息和用户信息
+                if (tradeRecords != null && !tradeRecords.isEmpty()) {
+                    for (SecondTradeRecord tradeRecord : tradeRecords) {
+                        // 获取完整的交易记录信息(包含买卖双方信息)
+                        SecondTradeRecordVo tradeRecordVo = secondTradeRecordMapper.getTradeRecordById(tradeRecord.getId());
+                        
+                        if (tradeRecordVo != null) {
+                            // 添加交易操作节点信息
+                            List<JSONObject> operationJsonList = platformSecondTradeService.getOperationJsonList(tradeRecord.getId());
+                            tradeRecordVo.setOperationJsonList(operationJsonList);
+                            
+                            tradeRecordVos.add(tradeRecordVo);
+                        }
+                    }
+                }
+            }
+            
+            detailVo.setTradeRecords(tradeRecordVos);
+            
+            return detailVo;
+        } catch (Exception e) {
+            log.error("SecondEntrustUserServiceImpl.getEntrustUserDetail error: {}", e.getMessage(), e);
+            throw new Exception("获取委托人详情失败: " + e.getMessage());
+        }
+    }
+
+}
+

+ 4 - 4
alien-second/src/main/java/shop/alien/second/service/impl/SecondRecommendServiceImpl.java

@@ -47,7 +47,7 @@ public class SecondRecommendServiceImpl extends ServiceImpl<SecondRecommendMappe
      */
     @Override
     public IPage<SecondGoodsRecommendVo> getSecondRecommendByPage(
-            IPage<SecondGoodsRecommendVo> page, String longitude, String latitude, Integer typeId) throws Exception {
+            IPage<SecondGoodsRecommendVo> page, String longitude, String latitude, Integer typeId, String radiusKm) throws Exception {
         try{
             JSONObject data = JwtUtil.getCurrentUserInfo();
             Integer userId = null;
@@ -60,7 +60,7 @@ public class SecondRecommendServiceImpl extends ServiceImpl<SecondRecommendMappe
             if (userId == null) {
                 return null;
             }
-            IPage<SecondGoodsRecommendVo> result = mapper.getSecondRecommendByPage(page, userId, longitude + "," + latitude, typeId, "user_" + phoneId);
+            IPage<SecondGoodsRecommendVo> result = mapper.getSecondRecommendByPage(page, userId, longitude + "," + latitude, typeId, "user_" + phoneId, radiusKm);
             for (SecondGoodsRecommendVo row : result.getRecords()) {
                 // 价格保留两位小数
                 row.setPrice(row.getAmount() != null ? row.getAmount().setScale(2, BigDecimal.ROUND_HALF_UP).toString() : null);
@@ -135,7 +135,7 @@ public class SecondRecommendServiceImpl extends ServiceImpl<SecondRecommendMappe
      * @return 关注列表
      */
     public IPage<SecondGoodsRecommendVo> querySecondNewGoodsByPage(
-            IPage<SecondGoodsRecommendVo> page, String position) throws Exception {
+            IPage<SecondGoodsRecommendVo> page, String position, String radiusKm) throws Exception {
         try {
             JSONObject data = JwtUtil.getCurrentUserInfo();
             String phoneId = null;
@@ -147,7 +147,7 @@ public class SecondRecommendServiceImpl extends ServiceImpl<SecondRecommendMappe
             if (StringUtil.isBlank(phoneId)) {
                 return null;
             }
-            IPage<SecondGoodsRecommendVo> list = mapper.querySecondNewGoodsByPage(page, userId,"user_" + phoneId, position);
+            IPage<SecondGoodsRecommendVo> list = mapper.querySecondNewGoodsByPage(page, userId,"user_" + phoneId, position, radiusKm);
             List<Integer> idList = list.getRecords().stream() // 创建流
                     .map(obj -> obj.getId())   // 提取每个元素的 ID
                     .collect(Collectors.toList());

+ 446 - 14
alien-second/src/main/java/shop/alien/second/service/impl/SecondTradeRecordServiceImpl.java

@@ -1,26 +1,31 @@
 package shop.alien.second.service.impl;
 
+import cn.hutool.core.collection.CollectionUtil;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
+import com.alibaba.nacos.common.utils.CollectionUtils;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import io.swagger.models.auth.In;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import shop.alien.config.properties.RiskControlProperties;
+import shop.alien.config.redis.BaseRedisService;
 import shop.alien.entity.result.BusinessException;
 import shop.alien.entity.second.*;
+import shop.alien.entity.second.vo.SecondEntrustUserDTO;
+import shop.alien.entity.store.*;
 import shop.alien.mapper.second.SecondRiskControlRecordMapper;
 import shop.alien.entity.second.vo.SecondTradeRecordVo;
-import shop.alien.entity.store.LifeMessage;
-import shop.alien.entity.store.LifeNotice;
-import shop.alien.entity.store.LifeUser;
-import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.second.vo.SellerEvaluationVo;
 import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.mapper.LifeMessageMapper;
 import shop.alien.mapper.LifeNoticeMapper;
@@ -32,10 +37,7 @@ import shop.alien.second.service.SecondTradeRecordService;
 import shop.alien.util.common.JwtUtil;
 
 import java.time.*;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.List;
-import java.util.Objects;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -63,14 +65,18 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
     private final LifeUserMapper lifeUserMapper;
     private final AlienStoreFeign alienStoreFeign;
     private final StoreDictionaryMapper storeDictionaryMapper;
+    private final BaseRedisService baseRedisService;
     @Autowired
     private RiskControlProperties riskControlProperties;
 
+    // 委托人信息
+    private final SecondEntrustUserMapper secondEntrustUserMapper;
+
     private final Object lock = new Object();
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean createTrade(SecondTradeRecord trade) throws Exception {
+    public boolean createTrade(SecondEntrustUserDTO trade) throws Exception {
         try {
             synchronized (lock) {
                 LocalDate now = LocalDate.now();
@@ -107,6 +113,17 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
             // 查询商品
             goods = secondGoodsMapper.selectById(trade.getGoodsId());
 
+            if (StringUtils.isNotBlank(trade.getEntrustUserPhone()) && StringUtils.isNotBlank(trade.getEntrustUserName()) && StringUtils.isNotBlank(trade.getEntrustIdCard())) {
+                // 添加商家委托信息
+                SecondEntrustUser secondEntrustUser = new SecondEntrustUser();
+                secondEntrustUser.setEntrustTradeId(trade.getId());
+                secondEntrustUser.setEntrustUserPhone(trade.getEntrustUserPhone());
+                secondEntrustUser.setEntrustUserName(trade.getEntrustUserName());
+                secondEntrustUser.setEntrustIdCard(trade.getEntrustIdCard());
+                secondEntrustUser.setEntrustTradeNo(trade.getTradeNo());
+                secondEntrustUserMapper.insert(secondEntrustUser);
+            }
+
             // 发送消息
             sendMsg(goods, trade, 1, "4");
 
@@ -138,6 +155,18 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
         message.put("transactionLocation", trade.getTransactionLocation());
         message.put("transactionTime", trade.getTransactionTime());
         message.put("tradeStatus", tradeStatus);
+
+        // 查询商家委托信息
+        LambdaQueryWrapper<SecondEntrustUser> entrustWrapper = new LambdaQueryWrapper<>();
+        entrustWrapper.eq(SecondEntrustUser::getEntrustTradeId, trade.getId());
+        SecondEntrustUser entrustUser = secondEntrustUserMapper.selectOne(entrustWrapper);
+        if ( entrustUser != null ) {
+            message.put("entrustId", entrustUser.getId());
+            message.put("entrustUserPhone", entrustUser.getEntrustUserPhone());
+            message.put("entrustUserName", entrustUser.getEntrustUserName());
+            message.put("entrustIdCard", entrustUser.getEntrustIdCard());
+        }
+
         if (6 == tradeStatus) {
             message.put("cancelUserId", trade.getCancelUserId());
             message.put("cancelReason", trade.getCancelReason());
@@ -461,10 +490,13 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
             if (userId == tradeRecord.getBuyerId()) {
                 recordVo.setUserTransactionStatus(tradeRecord.getBuyerTransactionStatus());
                 recordVo.setUserEvaluate(tradeRecord.getBuyerEvaluate());
+                recordVo.setUserRating(tradeRecord.getBuyerRating());
             } else if (userId == tradeRecord.getSellerId()) {
                 recordVo.setUserTransactionStatus(tradeRecord.getSellerTransactionStatus());
                 recordVo.setUserEvaluate(tradeRecord.getSellerEvaluate());
+                recordVo.setUserRating(tradeRecord.getSellerRating());
             }
+            recordVo.setSellerId(tradeRecord.getSellerId());
             return recordVo;
         } catch (Exception e) {
             log.error("SecondTradeRecordServiceImpl.getUserTradeStatus(): Error Msg={}", e.getMessage());
@@ -474,7 +506,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean tradeCompleteConfirm(int tradeId, int type, String evaluate) throws Exception {
+    public boolean tradeCompleteConfirm(int tradeId, int type, String evaluate, Integer rating) throws Exception {
         try {
             int userId = Objects.requireNonNull(JwtUtil.getCurrentUserInfo()).getInteger("userId");
             SecondTradeRecord tradeRecord = secondTradeRecordMapper.selectById(tradeId);
@@ -484,12 +516,16 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
             if (userId == tradeRecord.getBuyerId()) {
                 if (0 != tradeRecord.getBuyerTransactionStatus()) return false;
                 record.setBuyerTransactionStatus(type);
+                record.setBuyerCompleteTime(new Date());
                 record.setBuyerEvaluate(evaluate);
+                record.setBuyerRating(rating);
             // 卖家
             } else if (userId == tradeRecord.getSellerId()) {
                 if (0 != tradeRecord.getSellerTransactionStatus()) return false;
                 record.setSellerTransactionStatus(type);
+                record.setSellerCompleteTime(new Date());
                 record.setSellerEvaluate(evaluate);
+                record.setSellerRating(rating);
                 record.setTradeStatus(1 == type ? 4 : 5);
                 // 卖家如果选择交易成功  同步修改商品表
                 if (1 == type) {
@@ -552,7 +588,22 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
             wrapper.eq("trade.delete_flag", 0);
             wrapper.apply("((trade.buyer_id = '" + sideId + "' and trade.seller_id = '" + userId + "') || (trade.buyer_id = '" + userId + "' and trade.seller_id = '" + sideId + "'))");
             wrapper.orderByDesc("trade.created_time");
-            return secondTradeRecordMapper.getTradeRecord(wrapper);
+            List<SecondTradeRecordVo> vo = secondTradeRecordMapper.getTradeRecord(wrapper);
+            if (vo.size() > 0) {
+                for (SecondTradeRecordVo item : vo) {
+                    // 查询商家委托信息
+                    LambdaQueryWrapper<SecondEntrustUser> entrustWrapper = new LambdaQueryWrapper<>();
+                    entrustWrapper.eq(SecondEntrustUser::getEntrustTradeId, item.getId());
+                    SecondEntrustUser entrustUser = secondEntrustUserMapper.selectOne(entrustWrapper);
+                    if ( entrustUser != null ) {
+                        item.setEntrustId(entrustUser.getId());
+                        item.setEntrustUserPhone(entrustUser.getEntrustUserPhone());
+                        item.setEntrustUserName(entrustUser.getEntrustUserName());
+                        item.setEntrustIdCard(entrustUser.getEntrustIdCard());
+                    }
+                }
+            }
+            return vo;
         } catch (Exception e) {
             log.error("SecondTradeRecordServiceImpl.getTradeRecord(): Error Msg={}", e.getMessage());
             throw new Exception(e);
@@ -583,7 +634,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
                     .orElse(null);
 
             list = list.stream().filter(item -> item.getTransactionTime().equals(minDate)).collect(Collectors.toList());
-            return list.isEmpty() ? null : list.get(0);
+            return list.isEmpty() ? null : (1 == list.get(0).getBuyerSignIn() && 1 == list.get(0).getSellerSignIn() ? null : list.get(0));
          } catch (Exception e) {
             log.error("SecondTradeRecordServiceImpl.hasInTradeRecord(): Error Msg={}", e.getMessage());
             throw new Exception(e);
@@ -592,7 +643,8 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean modifyTradeRecord(int type, Integer tradeId, String transactionTime, String transactionLatitudeLongitude, String transactionLatitudeLongitudeAddress, String transactionLocation, String messageId) throws Exception {
+    public boolean modifyTradeRecord(int type, Integer tradeId, String transactionTime, String transactionLatitudeLongitude, String transactionLatitudeLongitudeAddress, String transactionLocation, String messageId,
+                                     String userPhone, String userName, String idCard, Integer entrustId) throws Exception {
         try {
             if (type == 1) {
                 LambdaUpdateWrapper<SecondTradeRecord> wrapper = new LambdaUpdateWrapper<>();
@@ -600,6 +652,15 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
                 wrapper.set(SecondTradeRecord::getTransactionLatitudeLongitude, transactionLatitudeLongitude);
                 wrapper.set(SecondTradeRecord::getTransactionLatitudeLongitudeAddress, transactionLatitudeLongitudeAddress);
                 wrapper.set(SecondTradeRecord::getTransactionLocation, transactionLocation);
+                // 清空签到信息
+                wrapper.set(SecondTradeRecord::getBuyerSignIn, 0);
+                wrapper.set(SecondTradeRecord::getBuyerSignInTime, null);
+                wrapper.set(SecondTradeRecord::getBuyerSignInLatitudeLongitude, null);
+                wrapper.set(SecondTradeRecord::getBuyerSignInLatitudeLongitudeAddress, null);
+                wrapper.set(SecondTradeRecord::getSellerSignIn, 0);
+                wrapper.set(SecondTradeRecord::getSellerSignInTime, null);
+                wrapper.set(SecondTradeRecord::getSellerSignInLatitudeLongitude, null);
+                wrapper.set(SecondTradeRecord::getSellerSignInLatitudeLongitudeAddress, null);
                 wrapper.eq(SecondTradeRecord::getId, tradeId);
                 secondTradeRecordMapper.update(null, wrapper);
             }
@@ -624,6 +685,35 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
             LifeUser lifeUser = lifeUserMapper.selectById(Objects.equals(userId, trade.getBuyerId()) ? trade.getSellerId() : trade.getBuyerId());
             String receiverId = "user_" + lifeUser.getUserPhone();
 
+            if (entrustId != null) {
+                LambdaUpdateWrapper<SecondEntrustUser> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.eq(SecondEntrustUser::getId, entrustId);
+                if (StringUtils.isBlank(userPhone) && StringUtils.isBlank(userName) && StringUtils.isBlank(idCard)) {
+                    updateWrapper.set(SecondEntrustUser::getDeleteFlag, 1);
+                } else {
+                    updateWrapper.set(SecondEntrustUser::getEntrustTradeId, tradeId);
+                    updateWrapper.set(SecondEntrustUser::getEntrustUserPhone, userPhone);
+                    updateWrapper.set(SecondEntrustUser::getEntrustUserName, userName);
+                    updateWrapper.set(SecondEntrustUser::getEntrustIdCard, idCard);
+                }
+                secondEntrustUserMapper.update(null, updateWrapper);
+            } else {
+                LambdaUpdateWrapper<SecondEntrustUser> wrapper = new LambdaUpdateWrapper<>();
+                wrapper.eq(SecondEntrustUser::getEntrustTradeId, tradeId);
+                Integer entrust = secondEntrustUserMapper.selectCount(wrapper);
+                if (entrust <= 0) {
+                    if (StringUtils.isNotBlank(userPhone) && StringUtils.isNotBlank(userName) && StringUtils.isNotBlank(idCard)) {
+                        secondEntrustUserMapper.insert(new SecondEntrustUser()
+                                .setEntrustTradeId(tradeId)
+                                .setEntrustUserPhone(userPhone)
+                                .setEntrustUserName(userName)
+                                .setEntrustIdCard(idCard)
+                                .setDeleteFlag(0));
+                    }
+                }
+            }
+
+
             // 给买家与卖家发送交易消息
             WebSocketVo webSocketVo = new WebSocketVo();
             webSocketVo.setSenderId(phoneId);
@@ -636,6 +726,11 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
 
             alienStoreFeign.sendMsgToClientByPhoneId(phoneId, JSONObject.from(webSocketVo).toJSONString());
             alienStoreFeign.sendMsgToClientByPhoneId(receiverId, JSONObject.from(webSocketVo).toJSONString());
+
+            // 交易时间小于十分钟,直接发送交易提醒
+            if (1 == type && Math.abs(Duration.between(Instant.now(), trade.getTransactionTime().toInstant()).getSeconds()) < 600) {
+                sendSignInMessage(trade);
+            }
             return true;
         } catch (Exception e) {
             log.error("SecondTradeRecordServiceImpl.modifyTradeRecord(): Error Msg={}", e.getMessage());
@@ -760,4 +855,341 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
             throw new Exception(e);
         }
     }
+
+    /**
+     * 获取用户作为卖家的交易评价列表(分页)
+     *
+     * @param sellerId 卖家ID(用户ID,必传)
+     * @param pageNum 页码
+     * @param pageSize 每页条数
+     * @return 卖家评价分页列表
+     */
+    @Override
+    public IPage<SellerEvaluationVo> getSellerEvaluationList(Integer sellerId, Integer pageNum, Integer pageSize) throws Exception {
+        try {
+            log.info("SecondTradeRecordServiceImpl.getSellerEvaluationList(): sellerId={}, pageNum={}, pageSize={}", 
+                    sellerId, pageNum, pageSize);
+            
+            // sellerId必传校验
+            if (sellerId == null) {
+                throw new Exception("卖家ID不能为空");
+            }
+            
+            // 参数默认值处理
+            if (pageNum == null || pageNum < 1) {
+                pageNum = 1;
+            }
+            if (pageSize == null || pageSize < 1) {
+                pageSize = 10;
+            }
+            // 限制最大每页条数
+            if (pageSize > 100) {
+                pageSize = 100;
+            }
+            
+            // 使用MyBatis Plus分页查询交易记录
+            Page<SecondTradeRecord> page = new Page<>(pageNum, pageSize);
+            LambdaQueryWrapper<SecondTradeRecord> tradeWrapper = new LambdaQueryWrapper<>();
+            tradeWrapper.eq(SecondTradeRecord::getSellerId, sellerId)
+                    .eq(SecondTradeRecord::getDeleteFlag, 0)
+                    .isNotNull(SecondTradeRecord::getBuyerEvaluate)
+                    .ne(SecondTradeRecord::getBuyerEvaluate, "")
+                    .orderByDesc(SecondTradeRecord::getCreatedTime);
+            
+            IPage<SecondTradeRecord> tradePage = secondTradeRecordMapper.selectPage(page, tradeWrapper);
+            List<SecondTradeRecord> tradeRecords = tradePage.getRecords();
+            
+            // 如果没有数据,直接返回空分页对象
+            if (tradeRecords.isEmpty()) {
+                log.info("SecondTradeRecordServiceImpl.getSellerEvaluationList(): 未查询到评价记录");
+                Page<SellerEvaluationVo> emptyPage = new Page<>(pageNum, pageSize);
+                emptyPage.setTotal(0);
+                return emptyPage;
+            }
+            
+            // 获取所有商品记录ID、买家ID和卖家ID
+            List<Integer> goodsRecordIds = tradeRecords.stream()
+                    .map(SecondTradeRecord::getGoodsRecordId)
+                    .filter(Objects::nonNull)
+                    .distinct()
+                    .collect(Collectors.toList());
+            
+            List<Integer> buyerIds = tradeRecords.stream()
+                    .map(SecondTradeRecord::getBuyerId)
+                    .filter(Objects::nonNull)
+                    .distinct()
+                    .collect(Collectors.toList());
+            
+            // 批量查询商品记录信息
+            java.util.Map<Integer, SecondGoodsRecord> goodsRecordMap = new java.util.HashMap<>();
+            if (!goodsRecordIds.isEmpty()) {
+                LambdaQueryWrapper<SecondGoodsRecord> goodsWrapper = new LambdaQueryWrapper<>();
+                goodsWrapper.in(SecondGoodsRecord::getId, goodsRecordIds);
+                List<SecondGoodsRecord> goodsRecords = secondGoodsRecordMapper.selectList(goodsWrapper);
+                goodsRecordMap = goodsRecords.stream()
+                        .collect(Collectors.toMap(SecondGoodsRecord::getId, g -> g));
+            }
+            
+            // 批量查询买家用户信息
+            java.util.Map<Integer, LifeUser> buyerMap = new java.util.HashMap<>();
+            if (!buyerIds.isEmpty()) {
+                LambdaQueryWrapper<LifeUser> userWrapper = new LambdaQueryWrapper<>();
+                userWrapper.in(LifeUser::getId, buyerIds)
+                        .eq(LifeUser::getDeleteFlag, 0);
+                List<LifeUser> buyers = lifeUserMapper.selectList(userWrapper);
+                buyerMap = buyers.stream()
+                        .collect(Collectors.toMap(LifeUser::getId, u -> u));
+            }
+            
+            // 查询卖家信息
+            LifeUser seller = lifeUserMapper.selectById(sellerId);
+            
+            // 批量查询买家和卖家的信用积分
+            List<Integer> allUserIds = new java.util.ArrayList<>(buyerIds);
+            allUserIds.add(sellerId);
+            java.util.Map<Integer, SecondUserCredit> creditMap = new java.util.HashMap<>();
+            if (!allUserIds.isEmpty()) {
+                LambdaQueryWrapper<SecondUserCredit> creditWrapper = new LambdaQueryWrapper<>();
+                creditWrapper.in(SecondUserCredit::getUserId, allUserIds)
+                        .eq(SecondUserCredit::getDeleteFlag, 0);
+                List<SecondUserCredit> credits = secondUserCreditMapper.selectList(creditWrapper);
+                creditMap = credits.stream()
+                        .collect(Collectors.toMap(SecondUserCredit::getUserId, c -> c));
+            }
+            
+            // 组装返回结果
+            List<SellerEvaluationVo> evaluationList = new java.util.ArrayList<>();
+            for (SecondTradeRecord trade : tradeRecords) {
+                SellerEvaluationVo vo = new SellerEvaluationVo();
+                
+                // 交易基本信息
+                vo.setId(trade.getId());
+                vo.setTradeNo(trade.getTradeNo());
+                vo.setGoodsId(trade.getGoodsId());
+                vo.setTransactionAmount(trade.getTransactionAmount());
+                vo.setBuyerId(trade.getBuyerId());
+                vo.setBuyerEvaluate(trade.getBuyerEvaluate());
+                // 卖家交易完成时间(评价时间,评分时间)
+                vo.setSellerCompleteTime(trade.getSellerCompleteTime());
+                // 买家交易完成时间(评价时间,评分时间)
+                vo.setBuyerCompleteTime(trade.getBuyerCompleteTime());
+                vo.setBuyerRating(trade.getBuyerRating());
+                vo.setTransactionTime(trade.getTransactionTime());
+                vo.setTradeStatus(trade.getTradeStatus());
+                vo.setCreatedTime(trade.getCreatedTime());
+                
+                // 商品信息
+                SecondGoodsRecord goodsRecord = goodsRecordMap.get(trade.getGoodsRecordId());
+                if (goodsRecord != null) {
+                    vo.setGoodsTitle(goodsRecord.getTitle());
+                    vo.setGoodsHomeImage(goodsRecord.getHomeImage());
+                    vo.setGoodsPrice(goodsRecord.getPrice());
+                }
+                
+                // 买家用户信息
+                LifeUser buyer = buyerMap.get(trade.getBuyerId());
+                if (buyer != null) {
+                    vo.setBuyerName(buyer.getUserName());
+                    vo.setBuyerImage(buyer.getUserImage());
+                    vo.setBuyerPhone(buyer.getUserPhone());
+                    vo.setBuyerRealName(buyer.getRealName());
+                    vo.setBuyerSex(buyer.getUserSex());
+                    vo.setBuyerJianjie(buyer.getJianjie());
+                }
+                
+                // 买家信用积分和风控评分等级
+                SecondUserCredit buyerCredit = creditMap.get(trade.getBuyerId());
+                if (buyerCredit != null) {
+                    vo.setBuyerCreditScore(buyerCredit.getUserPoints());
+                } else {
+                    vo.setBuyerCreditScore(0);
+                }
+                vo.setBuyerCreditLevel(calculateCreditLevel(trade.getBuyerId()));
+                
+                // 卖家信息
+                vo.setSellerId(sellerId);
+                if (seller != null) {
+                    vo.setSellerName(seller.getUserName());
+                    vo.setSellerImage(seller.getUserImage());
+                }
+                
+                // 卖家信用积分和风控评分等级
+                SecondUserCredit sellerCredit = creditMap.get(sellerId);
+                if (sellerCredit != null) {
+                    vo.setSellerCreditScore(sellerCredit.getUserPoints());
+                } else {
+                    vo.setSellerCreditScore(0);
+                }
+                vo.setSellerCreditLevel(calculateCreditLevel(sellerId));
+                
+                evaluationList.add(vo);
+            }
+            
+            // 构建分页返回对象
+            Page<SellerEvaluationVo> resultPage = new Page<>(pageNum, pageSize);
+            resultPage.setRecords(evaluationList);
+            resultPage.setTotal(tradePage.getTotal());
+            resultPage.setPages(tradePage.getPages());
+            
+            log.info("SecondTradeRecordServiceImpl.getSellerEvaluationList(): 查询到{}条评价记录,共{}页", 
+                    evaluationList.size(), resultPage.getPages());
+            return resultPage;
+        } catch (Exception e) {
+            log.error("SecondTradeRecordServiceImpl.getSellerEvaluationList(): Error Msg={}", e.getMessage(), e);
+            throw new Exception("获取卖家评价列表失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public void locationShareAdd(Integer otherUserId) throws Exception {
+        try {
+            Integer userId = Objects.requireNonNull(JwtUtil.getCurrentUserInfo()).getInteger("userId");
+            String key = getKey(userId, otherUserId);
+
+            if (baseRedisService.hasKey(key)) {
+                JSONArray array = JSONArray.parseArray(baseRedisService.getString(key));
+                if (!array.contains(userId)) array.add(userId);
+                baseRedisService.setString(key, array.toJSONString());
+            } else {
+                JSONArray array = new JSONArray();
+                array.add(userId);
+                baseRedisService.setString(key, array.toJSONString());
+            }
+        } catch (Exception e) {
+            log.error("SecondTradeRecordServiceImpl.locationShareAdd(): Error Msg={}", e.getMessage(), e);
+            throw new Exception("添加位置共享失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public void locationShareDel(Integer otherUserId) throws Exception {
+        try {
+            Integer userId = Objects.requireNonNull(JwtUtil.getCurrentUserInfo()).getInteger("userId");
+            String key = getKey(userId, otherUserId);
+
+            // 存在key,则删除
+            if (baseRedisService.hasKey(key)) {
+                JSONArray array = JSONArray.parseArray(baseRedisService.getString(key));
+                array.remove(userId);
+                if (array.isEmpty()) {
+                    baseRedisService.delete(key);
+                    LifeUser user = lifeUserMapper.selectById(userId);
+                    LifeUser otherUser = lifeUserMapper.selectById(otherUserId);
+                    LambdaQueryWrapper<LifeMessage> wrapper = new LambdaQueryWrapper<>();
+                    wrapper.eq(LifeMessage::getType, 10);
+                    wrapper.isNull(LifeMessage::getContent);
+                    wrapper.apply("((receiver_id = 'user_" + otherUser.getUserPhone() + "' and sender_id = 'user_" + user.getUserPhone() + "') or (receiver_id = 'user_" + user.getUserPhone() + "' and sender_id = 'user_" + otherUser.getUserPhone() + "'))");
+                    List<Integer> messageIds = lifeMessageMapper.selectList(wrapper).stream().map(LifeMessage::getId).collect(Collectors.toList());
+
+                    if (CollectionUtils.isNotEmpty(messageIds)) {
+                        // 修改消息状态
+                        JSONObject messageJson = new JSONObject();
+                        messageJson.put("cardType", 2);
+                        LambdaUpdateWrapper<LifeMessage> updateWrapper = new LambdaUpdateWrapper<>();
+                        updateWrapper.in(LifeMessage::getId, messageIds);
+                        updateWrapper.set(LifeMessage::getContent, messageJson.toJSONString());
+                        lifeMessageMapper.update(null, updateWrapper);
+
+                        // 发起交易人信息
+                        String phoneId = JwtUtil.getCurrentUserInfo().getString("userType") + "_" + JwtUtil.getCurrentUserInfo().getString("phone");
+
+                        // 获取交易对方信息
+                        LifeUser lifeUser = lifeUserMapper.selectById(otherUserId);
+                        String receiverId = "user_" + lifeUser.getUserPhone();
+
+                        // 给买家与卖家发送交易消息
+                        WebSocketVo webSocketVo = new WebSocketVo();
+                        webSocketVo.setSenderId(phoneId);
+                        webSocketVo.setReceiverId(receiverId);
+                        webSocketVo.setCategory("message");
+                        webSocketVo.setType("10");
+                        webSocketVo.setIsRead(0);
+                        webSocketVo.setText(messageJson.toJSONString());
+                        webSocketVo.setMessageIdList(messageIds);
+
+                        alienStoreFeign.sendMsgToClientByPhoneId(phoneId, JSONObject.from(webSocketVo).toJSONString());
+                        alienStoreFeign.sendMsgToClientByPhoneId(receiverId, JSONObject.from(webSocketVo).toJSONString());
+                    }
+                } else {
+                    baseRedisService.setString(key, array.toJSONString());
+                }
+            }
+        } catch (Exception e) {
+            log.error("SecondTradeRecordServiceImpl.locationShareDel(): Error Msg={}", e.getMessage(), e);
+            throw new Exception("删除位置共享失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public boolean locationShareHas(Integer otherUserId) throws Exception {
+        try {
+            Integer userId = Objects.requireNonNull(JwtUtil.getCurrentUserInfo()).getInteger("userId");
+            String key = getKey(userId, otherUserId);
+
+            // 是否存在定位信息
+            if (baseRedisService.hasKey(key) && !JSONArray.parseArray(baseRedisService.getString(key)).isEmpty()) {
+                return true;
+            } else {
+                baseRedisService.delete(key);
+            }
+
+        } catch (Exception e) {
+            log.error("SecondTradeRecordServiceImpl.locationShareHas(): Error Msg={}", e.getMessage(), e);
+            throw new Exception("获取位置共享状态失败: " + e.getMessage());
+        }
+        return false;
+    }
+
+    private String getKey(Integer userId, Integer otherUserId) {
+        // 组合userId和otherUserId作为Redis的key
+        // 使用排序方式确保key的一致性(较小的ID在前)
+        String key;
+        if (userId.compareTo(otherUserId) < 0) {
+            key = "second_location_share:" + userId + ":" + otherUserId;
+        } else {
+            key = "second_location_share:" + otherUserId + ":" + userId;
+        }
+        return key;
+    }
+
+    /**
+     * 计算用户风控评分等级
+     * 根据用户违规次数计算等级:O(无记录) < A(1-2次) < B(3-5次) < C(6-9次) < D(10-14次) < E(≥15次)
+     *
+     * @param userId 用户ID
+     * @return 风控评分等级
+     */
+    private String calculateCreditLevel(Integer userId) {
+        try {
+            if (userId == null) {
+                return "O";
+            }
+            
+            // 查询用户违规记录数量
+            LambdaQueryWrapper<SecondRiskControlRecord> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(SecondRiskControlRecord::getUserId, userId)
+                    .eq(SecondRiskControlRecord::getDeleteFlag, 0);
+            
+            Integer count = secondRiskControlRecordMapper.selectCount(wrapper);
+            int violationCount = count != null ? count : 0;
+            
+            // 根据违规次数确定等级
+            if (violationCount == 0) {
+                return "O";  // 无记录
+            } else if (violationCount >= 15) {
+                return "E";  // 最差
+            } else if (violationCount >= 10) {
+                return "D";
+            } else if (violationCount >= 6) {
+                return "C";
+            } else if (violationCount >= 3) {
+                return "B";
+            } else {
+                return "A";  // 1-2次违规
+            }
+        } catch (Exception e) {
+            log.error("SecondTradeRecordServiceImpl.calculateCreditLevel(): Error Msg={}", e.getMessage(), e);
+            return "O";  // 异常时返回默认等级
+        }
+    }
 }

+ 171 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StoreLicenseHistoryController.java

@@ -0,0 +1,171 @@
+package shop.alien.storeplatform.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreLicenseHistory;
+import shop.alien.entity.store.dto.StoreLicenseHistoryDTO;
+import shop.alien.entity.store.vo.StoreLicenseHistoryVO;
+import shop.alien.storeplatform.service.StoreLicenseHistoryService;
+
+import java.util.List;
+
+/**
+ * 商户证照历史记录 Controller
+ *
+ * @author system
+ * @since 2025-11-24
+ */
+@Slf4j
+@Api(tags = {"商家端-证照历史管理"})
+@ApiSort(5)
+@CrossOrigin
+@RestController
+@RequestMapping("/storeLicenseHistory")
+@RequiredArgsConstructor
+public class StoreLicenseHistoryController {
+
+    private final StoreLicenseHistoryService storeLicenseHistoryService;
+
+    @ApiOperation("创建证照历史记录")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/create")
+    public R<StoreLicenseHistory> createLicenseHistory(@RequestBody @Validated StoreLicenseHistoryDTO dto) {
+        log.info("StoreLicenseHistoryController.createLicenseHistory: dto={}", dto);
+        try {
+            StoreLicenseHistory result = storeLicenseHistoryService.createLicenseHistory(dto);
+            return R.data(result, "创建成功");
+        } catch (Exception e) {
+            log.error("StoreLicenseHistoryController.createLicenseHistory ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("更新证照历史记录")
+    @ApiOperationSupport(order = 2)
+    @PutMapping("/update")
+    public R<Boolean> updateLicenseHistory(@RequestBody @Validated StoreLicenseHistoryDTO dto) {
+        log.info("StoreLicenseHistoryController.updateLicenseHistory: dto={}", dto);
+        try {
+            boolean result = storeLicenseHistoryService.updateLicenseHistory(dto);
+            if (result) {
+                return R.data(true, "更新成功");
+            }
+            return R.fail("更新失败");
+        } catch (Exception e) {
+            log.error("StoreLicenseHistoryController.updateLicenseHistory ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("删除证照历史记录")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "记录ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @DeleteMapping("/delete")
+    public R<Boolean> deleteLicenseHistory(@RequestParam("id") Integer id) {
+        log.info("StoreLicenseHistoryController.deleteLicenseHistory: id={}", id);
+        try {
+            boolean result = storeLicenseHistoryService.deleteLicenseHistory(id);
+            if (result) {
+                return R.data(true, "删除成功");
+            }
+            return R.fail("删除失败");
+        } catch (Exception e) {
+            log.error("StoreLicenseHistoryController.deleteLicenseHistory ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据ID获取证照历史记录详情")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "记录ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getById")
+    public R<StoreLicenseHistoryVO> getLicenseHistoryById(@RequestParam("id") Integer id) {
+        log.info("StoreLicenseHistoryController.getLicenseHistoryById: id={}", id);
+        try {
+            StoreLicenseHistoryVO result = storeLicenseHistoryService.getLicenseHistoryById(id);
+            if (result != null) {
+                return R.data(result);
+            }
+            return R.fail("记录不存在");
+        } catch (Exception e) {
+            log.error("StoreLicenseHistoryController.getLicenseHistoryById ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据商户ID获取证照历史记录列表")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "商户ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/listByStoreId")
+    public R<List<StoreLicenseHistoryVO>> getLicenseHistoryListByStoreId(@RequestParam("storeId") Integer storeId) {
+        log.info("StoreLicenseHistoryController.getLicenseHistoryListByStoreId: storeId={}", storeId);
+        try {
+            List<StoreLicenseHistoryVO> result = storeLicenseHistoryService.getLicenseHistoryListByStoreId(storeId);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("StoreLicenseHistoryController.getLicenseHistoryListByStoreId ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据商户ID和证照类型获取证照历史记录列表")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "商户ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "licenseStatus", value = "证照类型: 1-合同管理, 2-食品经营许可证", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/listByStoreIdAndType")
+    public R<List<StoreLicenseHistoryVO>> getLicenseHistoryListByStoreIdAndType(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam("licenseStatus") Integer licenseStatus) {
+        log.info("StoreLicenseHistoryController.getLicenseHistoryListByStoreIdAndType: storeId={}, licenseStatus={}", 
+                 storeId, licenseStatus);
+        try {
+            List<StoreLicenseHistoryVO> result = storeLicenseHistoryService.getLicenseHistoryListByStoreIdAndType(storeId, licenseStatus);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("StoreLicenseHistoryController.getLicenseHistoryListByStoreIdAndType ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("分页查询证照历史记录")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "current", value = "当前页", dataType = "Long", paramType = "query", required = true, defaultValue = "1"),
+            @ApiImplicitParam(name = "size", value = "每页大小", dataType = "Long", paramType = "query", required = true, defaultValue = "10"),
+            @ApiImplicitParam(name = "storeId", value = "商户ID(可选)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "licenseStatus", value = "证照类型(可选): 1-合同管理, 2-食品经营许可证", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "licenseExecuteStatus", value = "审核状态(可选): 1-审核中, 2-审核拒绝, 3-审核通过", dataType = "Integer", paramType = "query", required = false)
+    })
+    @GetMapping("/page")
+    public R<Page<StoreLicenseHistoryVO>> getLicenseHistoryPage(
+            @RequestParam(value = "current", defaultValue = "1") Long current,
+            @RequestParam(value = "size", defaultValue = "10") Long size,
+            @RequestParam(value = "storeId", required = false) Integer storeId,
+            @RequestParam(value = "licenseStatus", required = false) Integer licenseStatus,
+            @RequestParam(value = "licenseExecuteStatus", required = false) Integer licenseExecuteStatus) {
+        log.info("StoreLicenseHistoryController.getLicenseHistoryPage: current={}, size={}, storeId={}, licenseStatus={}, licenseExecuteStatus={}", 
+                 current, size, storeId, licenseStatus, licenseExecuteStatus);
+        try {
+            Page<StoreLicenseHistory> page = new Page<>(current, size);
+            Page<StoreLicenseHistoryVO> result = storeLicenseHistoryService.getLicenseHistoryPage(page, storeId, licenseStatus, licenseExecuteStatus);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("StoreLicenseHistoryController.getLicenseHistoryPage ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}
+

+ 42 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformInfoController.java

@@ -0,0 +1,42 @@
+package shop.alien.storeplatform.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import io.swagger.annotations.ApiSort;
+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.StoreInfo;
+import shop.alien.entity.store.dto.StoreInfoDto;
+import shop.alien.storeplatform.service.StorePlatformInfoService;
+
+@Slf4j
+@Api(tags = {"商户平台-门店信息"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/storePlatformInfo")
+@RequiredArgsConstructor
+public class StorePlatformInfoController {
+
+    private final StorePlatformInfoService storePlatformInfoService;
+
+    @ApiOperation("门店装修-编辑门店信息")
+    @ApiOperationSupport(order = 3)
+    @PostMapping("/saveOrUpdate")
+    public R<StoreInfo> saveOrUpdate(@RequestBody StoreInfoDto storeInfo) {
+        log.info("StoreInfoController.saveOrUpdate?storeInfo={}", storeInfo);
+        try {
+            int num = storePlatformInfoService.saveOrUpdateStoreInfo(storeInfo);
+            if (num > 0) {
+                return R.success("成功");
+            }
+            return R.fail("失败");
+        } catch (Exception e) {
+            return R.fail(e.getMessage());
+        }
+    }
+
+}

+ 12 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/NearMeService.java

@@ -0,0 +1,12 @@
+package shop.alien.storeplatform.service;
+
+import org.springframework.data.geo.Point;
+import shop.alien.entity.store.dto.NearMeDto;
+
+import java.util.List;
+
+public interface NearMeService {
+
+    void inGeolocation(Point point, String content, Boolean flag);
+
+}

+ 79 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StoreLicenseHistoryService.java

@@ -0,0 +1,79 @@
+package shop.alien.storeplatform.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import shop.alien.entity.store.StoreLicenseHistory;
+import shop.alien.entity.store.dto.StoreLicenseHistoryDTO;
+import shop.alien.entity.store.vo.StoreLicenseHistoryVO;
+
+import java.util.List;
+
+/**
+ * 商户证照历史记录 Service 接口
+ *
+ * @author system
+ * @since 2025-11-24
+ */
+public interface StoreLicenseHistoryService {
+
+    /**
+     * 创建证照历史记录
+     *
+     * @param dto 证照历史记录DTO
+     * @return 创建结果
+     */
+    StoreLicenseHistory createLicenseHistory(StoreLicenseHistoryDTO dto);
+
+    /**
+     * 更新证照历史记录
+     *
+     * @param dto 证照历史记录DTO
+     * @return 更新结果
+     */
+    boolean updateLicenseHistory(StoreLicenseHistoryDTO dto);
+
+    /**
+     * 根据ID删除证照历史记录
+     *
+     * @param id 记录ID
+     * @return 删除结果
+     */
+    boolean deleteLicenseHistory(Integer id);
+
+    /**
+     * 根据ID获取证照历史记录详情
+     *
+     * @param id 记录ID
+     * @return 证照历史记录详情
+     */
+    StoreLicenseHistoryVO getLicenseHistoryById(Integer id);
+
+    /**
+     * 根据商户ID获取证照历史记录列表
+     *
+     * @param storeId 商户ID
+     * @return 证照历史记录列表
+     */
+    List<StoreLicenseHistoryVO> getLicenseHistoryListByStoreId(Integer storeId);
+
+    /**
+     * 根据商户ID和证照类型获取证照历史记录列表
+     *
+     * @param storeId       商户ID
+     * @param licenseStatus 证照类型
+     * @return 证照历史记录列表
+     */
+    List<StoreLicenseHistoryVO> getLicenseHistoryListByStoreIdAndType(Integer storeId, Integer licenseStatus);
+
+    /**
+     * 分页查询证照历史记录
+     *
+     * @param page          分页对象
+     * @param storeId       商户ID(可选)
+     * @param licenseStatus 证照类型(可选)
+     * @param licenseExecuteStatus 审核状态(可选)
+     * @return 分页结果
+     */
+    Page<StoreLicenseHistoryVO> getLicenseHistoryPage(Page<StoreLicenseHistory> page, Integer storeId, 
+                                                       Integer licenseStatus, Integer licenseExecuteStatus);
+}
+

+ 16 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformInfoService.java

@@ -0,0 +1,16 @@
+package shop.alien.storeplatform.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.StoreInfo;
+import shop.alien.entity.store.dto.StoreInfoDto;
+
+public interface StorePlatformInfoService extends IService<StoreInfo> {
+
+    /**
+     * 门店装修 - 编辑
+     * @param storeInfo
+     * @return
+     */
+    int saveOrUpdateStoreInfo(StoreInfoDto storeInfo);
+
+}

+ 27 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/NearMeServiceImpl.java

@@ -0,0 +1,27 @@
+package shop.alien.storeplatform.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.geo.Point;
+import org.springframework.stereotype.Service;
+import shop.alien.config.redis.BaseRedisService;
+import shop.alien.storeplatform.service.NearMeService;
+import shop.alien.util.common.constant.Constant;
+
+@Service
+@RequiredArgsConstructor
+public class NearMeServiceImpl implements NearMeService {
+
+    private final BaseRedisService baseRedisService;
+
+    /**
+     * 添加地址位置
+     *
+     * @param point
+     * @param content
+     */
+    @Override
+    public void inGeolocation(Point point, String content, Boolean flag) {
+        baseRedisService.inGeolocation(point, content, flag ? Constant.GEO_STORE_PRIMARY : Constant.GEO_STORE_NAME);
+    }
+
+}

+ 241 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreLicenseHistoryServiceImpl.java

@@ -0,0 +1,241 @@
+package shop.alien.storeplatform.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 lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.result.BusinessException;
+import shop.alien.entity.store.StoreLicenseHistory;
+import shop.alien.entity.store.StoreInfo;
+import shop.alien.entity.store.dto.StoreLicenseHistoryDTO;
+import shop.alien.entity.store.vo.StoreLicenseHistoryVO;
+import shop.alien.mapper.StoreLicenseHistoryMapper;
+import shop.alien.mapper.StoreInfoMapper;
+import shop.alien.storeplatform.service.StoreLicenseHistoryService;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 商户证照历史记录 Service 实现类
+ *
+ * @author system
+ * @since 2025-11-24
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StoreLicenseHistoryServiceImpl implements StoreLicenseHistoryService {
+
+    private final StoreLicenseHistoryMapper storeLicenseHistoryMapper;
+    private final StoreInfoMapper storeInfoMapper;
+
+    @Override
+    public StoreLicenseHistory createLicenseHistory(StoreLicenseHistoryDTO dto) {
+        log.info("StoreLicenseHistoryServiceImpl.createLicenseHistory: dto={}", dto);
+        
+        // 验证商户是否存在
+        StoreInfo storeInfo = storeInfoMapper.selectById(dto.getStoreId());
+        if (storeInfo == null) {
+            throw new BusinessException("商户不存在");
+        }
+
+        // 验证证照类型
+        if (dto.getLicenseStatus() == null || (dto.getLicenseStatus() != 1 && dto.getLicenseStatus() != 2)) {
+            throw new BusinessException("证照类型无效");
+        }
+
+        // 验证审核状态
+        if (dto.getLicenseExecuteStatus() == null || 
+            (dto.getLicenseExecuteStatus() < 1 || dto.getLicenseExecuteStatus() > 3)) {
+            throw new BusinessException("审核状态无效");
+        }
+
+        StoreLicenseHistory entity = new StoreLicenseHistory();
+        BeanUtils.copyProperties(dto, entity);
+        
+        int result = storeLicenseHistoryMapper.insert(entity);
+        if (result > 0) {
+            return entity;
+        }
+        throw new BusinessException("创建证照历史记录失败");
+    }
+
+    @Override
+    public boolean updateLicenseHistory(StoreLicenseHistoryDTO dto) {
+        log.info("StoreLicenseHistoryServiceImpl.updateLicenseHistory: dto={}", dto);
+        
+        if (dto.getId() == null) {
+            throw new BusinessException("记录ID不能为空");
+        }
+
+        StoreLicenseHistory existingRecord = storeLicenseHistoryMapper.selectById(dto.getId());
+        if (existingRecord == null) {
+            throw new BusinessException("证照历史记录不存在");
+        }
+
+        StoreLicenseHistory entity = new StoreLicenseHistory();
+        BeanUtils.copyProperties(dto, entity);
+        
+        int result = storeLicenseHistoryMapper.updateById(entity);
+        return result > 0;
+    }
+
+    @Override
+    public boolean deleteLicenseHistory(Integer id) {
+        log.info("StoreLicenseHistoryServiceImpl.deleteLicenseHistory: id={}", id);
+        
+        if (id == null) {
+            throw new BusinessException("记录ID不能为空");
+        }
+
+        StoreLicenseHistory existingRecord = storeLicenseHistoryMapper.selectById(id);
+        if (existingRecord == null) {
+            throw new BusinessException("证照历史记录不存在");
+        }
+
+        // 逻辑删除
+        int result = storeLicenseHistoryMapper.deleteById(id);
+        return result > 0;
+    }
+
+    @Override
+    public StoreLicenseHistoryVO getLicenseHistoryById(Integer id) {
+        log.info("StoreLicenseHistoryServiceImpl.getLicenseHistoryById: id={}", id);
+        
+        if (id == null) {
+            throw new BusinessException("记录ID不能为空");
+        }
+
+        StoreLicenseHistory entity = storeLicenseHistoryMapper.selectById(id);
+        if (entity == null) {
+            return null;
+        }
+
+        return convertToVO(entity);
+    }
+
+    @Override
+    public List<StoreLicenseHistoryVO> getLicenseHistoryListByStoreId(Integer storeId) {
+        log.info("StoreLicenseHistoryServiceImpl.getLicenseHistoryListByStoreId: storeId={}", storeId);
+        
+        if (storeId == null) {
+            throw new BusinessException("商户ID不能为空");
+        }
+
+        LambdaQueryWrapper<StoreLicenseHistory> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreLicenseHistory::getStoreId, storeId)
+               .orderByDesc(StoreLicenseHistory::getCreatedTime);
+        
+        List<StoreLicenseHistory> list = storeLicenseHistoryMapper.selectList(wrapper);
+        return list.stream().map(this::convertToVO).collect(Collectors.toList());
+    }
+
+    @Override
+    public List<StoreLicenseHistoryVO> getLicenseHistoryListByStoreIdAndType(Integer storeId, Integer licenseStatus) {
+        log.info("StoreLicenseHistoryServiceImpl.getLicenseHistoryListByStoreIdAndType: storeId={}, licenseStatus={}", 
+                 storeId, licenseStatus);
+        
+        if (storeId == null) {
+            throw new BusinessException("商户ID不能为空");
+        }
+        if (licenseStatus == null) {
+            throw new BusinessException("证照类型不能为空");
+        }
+
+        LambdaQueryWrapper<StoreLicenseHistory> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreLicenseHistory::getStoreId, storeId)
+               .eq(StoreLicenseHistory::getLicenseStatus, licenseStatus)
+               .orderByDesc(StoreLicenseHistory::getCreatedTime);
+        
+        List<StoreLicenseHistory> list = storeLicenseHistoryMapper.selectList(wrapper);
+        return list.stream().map(this::convertToVO).collect(Collectors.toList());
+    }
+
+    @Override
+    public Page<StoreLicenseHistoryVO> getLicenseHistoryPage(Page<StoreLicenseHistory> page, Integer storeId,
+                                                              Integer licenseStatus, Integer licenseExecuteStatus) {
+        log.info("StoreLicenseHistoryServiceImpl.getLicenseHistoryPage: page={}, storeId={}, licenseStatus={}, licenseExecuteStatus={}", 
+                 page, storeId, licenseStatus, licenseExecuteStatus);
+        
+        LambdaQueryWrapper<StoreLicenseHistory> wrapper = new LambdaQueryWrapper<>();
+        
+        if (storeId != null) {
+            wrapper.eq(StoreLicenseHistory::getStoreId, storeId);
+        }
+        if (licenseStatus != null) {
+            wrapper.eq(StoreLicenseHistory::getLicenseStatus, licenseStatus);
+        }
+        if (licenseExecuteStatus != null) {
+            wrapper.eq(StoreLicenseHistory::getLicenseExecuteStatus, licenseExecuteStatus);
+        }
+        
+        wrapper.orderByDesc(StoreLicenseHistory::getCreatedTime);
+        
+        IPage<StoreLicenseHistory> entityPage = storeLicenseHistoryMapper.selectPage(page, wrapper);
+        
+        // 转换为VO
+        Page<StoreLicenseHistoryVO> voPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
+        List<StoreLicenseHistoryVO> voList = entityPage.getRecords().stream()
+                .map(this::convertToVO)
+                .collect(Collectors.toList());
+        voPage.setRecords(voList);
+        
+        return voPage;
+    }
+
+    /**
+     * 实体转VO
+     */
+    private StoreLicenseHistoryVO convertToVO(StoreLicenseHistory entity) {
+        StoreLicenseHistoryVO vo = new StoreLicenseHistoryVO();
+        BeanUtils.copyProperties(entity, vo);
+        
+        // 获取商户名称
+        if (entity.getStoreId() != null) {
+            StoreInfo storeInfo = storeInfoMapper.selectById(entity.getStoreId());
+            if (storeInfo != null) {
+                vo.setStoreName(storeInfo.getStoreName());
+            }
+        }
+        
+        // 设置证照类型名称
+        if (entity.getLicenseStatus() != null) {
+            switch (entity.getLicenseStatus()) {
+                case 1:
+                    vo.setLicenseStatusName("合同管理");
+                    break;
+                case 2:
+                    vo.setLicenseStatusName("食品经营许可证");
+                    break;
+                default:
+                    vo.setLicenseStatusName("未知");
+                    break;
+            }
+        }
+        
+        // 设置审核状态名称
+        if (entity.getLicenseExecuteStatus() != null) {
+            switch (entity.getLicenseExecuteStatus()) {
+                case 1:
+                    vo.setLicenseExecuteStatusName("审核中");
+                    break;
+                case 2:
+                    vo.setLicenseExecuteStatusName("审核拒绝");
+                    break;
+                case 3:
+                    vo.setLicenseExecuteStatusName("审核通过");
+                    break;
+                default:
+                    vo.setLicenseExecuteStatusName("未知");
+                    break;
+            }
+        }
+        
+        return vo;
+    }
+}
+

+ 196 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformInfoServiceImpl.java

@@ -0,0 +1,196 @@
+package shop.alien.storeplatform.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.geo.Point;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import shop.alien.entity.store.*;
+import shop.alien.entity.store.dto.StoreInfoDto;
+import shop.alien.mapper.*;
+import shop.alien.storeplatform.service.NearMeService;
+import shop.alien.storeplatform.service.StorePlatformInfoService;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class StorePlatformInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo> implements StorePlatformInfoService {
+
+    private final LifeUserOrderMapper lifeUserOrderMapper;
+
+    private final LifeGroupBuyMainMapper lifeGroupBuyMainMapper;
+
+    private final LifeCouponMapper lifeCouponMapper;
+
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    private final StoreInfoMapper storeInfoMapper;
+
+    private final EssentialCityCodeMapper essentialCityCodeMapper;
+
+    private final NearMeService nearMeService;
+
+    @Override
+    public int saveOrUpdateStoreInfo(StoreInfoDto storeInfodto) {
+        if (storeInfodto.getId() != null) {
+            // 校验当前店铺存在未完成的订单及正在销售的商品
+            verificationStoreInfoStatus(storeInfodto);
+
+            // 1. 处理经营板块及经营种类
+            Integer businessSection = storeInfodto.getBusinessSection();
+            StoreDictionary businessSectionDict = null;
+            List<String> businessTypeNames = new ArrayList<>();
+
+            // 查询经营板块(非空判断,避免后续空指针)
+            if (Objects.nonNull(businessSection)) {
+                businessSectionDict = storeDictionaryMapper.selectOne(
+                        new LambdaQueryWrapper<StoreDictionary>()
+                                .eq(StoreDictionary::getDictId, businessSection)
+                                .eq(StoreDictionary::getTypeName, "business_section")
+                );
+                // 若经营板块不存在,可根据业务抛出异常或默认处理
+                if (Objects.isNull(businessSectionDict)) {
+                    throw new IllegalArgumentException("经营板块不存在:" + businessSection);
+                }
+            }
+
+            // 批量查询经营种类(替代循环查询,解决N+1问题)
+            List<String> businessTypes = storeInfodto.getBusinessTypes();
+            if (Objects.nonNull(businessSectionDict) && !CollectionUtils.isEmpty(businessTypes)) {
+                // 一次查询所有符合条件的经营种类
+                List<StoreDictionary> typeDicts = storeDictionaryMapper.selectList(
+                        new LambdaQueryWrapper<StoreDictionary>()
+                                .in(StoreDictionary::getDictId, businessTypes) // 批量匹配id
+                                .eq(StoreDictionary::getParentId, businessSectionDict.getId())
+                );
+                // 转为Map<dictId, StoreDictionary>,方便快速获取
+                Map<String, StoreDictionary> typeDictMap = typeDicts.stream()
+                        .collect(Collectors.toMap(
+                                dict -> dict.getDictId().toString(), // 假设dictId是字符串类型,若为数字需转换
+                                Function.identity(),
+                                (existing, replacement) -> existing // 处理重复id(理论上不会有)
+                        ));
+                // 提取经营种类名称
+                for (String typeId : businessTypes) {
+                    StoreDictionary dict = typeDictMap.get(typeId);
+                    if (Objects.nonNull(dict)) {
+                        businessTypeNames.add(dict.getDictDetail());
+                    } else {
+                        // 可选:记录无效的经营种类id,便于排查
+                        log.warn("无效的经营种类id:" + typeId + "(所属板块:" + businessSectionDict.getDictDetail() + ")");
+                    }
+                }
+            }
+
+            // 2. 构建StoreInfo对象
+            StoreInfo storeInfo = new StoreInfo();
+            BeanUtils.copyProperties(storeInfodto, storeInfo);
+
+            // 3. 处理经纬度
+            String longitude = storeInfodto.getStorePositionLongitude();
+            String latitude = storeInfodto.getStorePositionLatitude();
+            if (StringUtils.isEmpty(longitude) || StringUtils.isEmpty(latitude)) {
+                // 经纬度为空时,复用原数据
+                StoreInfo oldStore = storeInfoMapper.selectById(storeInfodto.getId());
+                if (Objects.nonNull(oldStore)) {
+                    storeInfo.setStorePosition(oldStore.getStorePosition());
+                }
+            } else {
+                // 拼接经纬度
+                storeInfo.setStorePosition(longitude + "," + latitude);
+            }
+
+            // 4. 设置门店状态、经营板块及类型
+            storeInfo.setStoreStatus(storeInfodto.getStoreStatus());
+            // 经营板块(非空时才设置)
+            if (Objects.nonNull(businessSection)) {
+                storeInfo.setBusinessSection(businessSection);
+            }
+            // 经营板块名称(非空时才设置)
+            if (Objects.nonNull(businessSectionDict)) {
+                storeInfo.setBusinessSectionName(businessSectionDict.getDictDetail());
+            }
+            // 经营种类及名称(非空时拼接)
+            if (!CollectionUtils.isEmpty(businessTypes) && !CollectionUtils.isEmpty(businessTypeNames)) {
+                storeInfo.setBusinessTypes(String.join(",", businessTypes));
+                storeInfo.setBusinessTypesName(String.join(",", businessTypeNames));
+            }
+
+            // 5. 处理行政区域信息(抽取为方法,减少重复) 不存在单独查的情况
+            if (!Objects.isNull(storeInfo.getAdministrativeRegionProvinceAdcode())) {
+
+                storeInfo.setAdministrativeRegionProvinceName(
+                        getAreaName(storeInfo.getAdministrativeRegionProvinceAdcode())
+                );
+                storeInfo.setAdministrativeRegionCityName(
+                        getAreaName(storeInfo.getAdministrativeRegionCityAdcode())
+                );
+                storeInfo.setAdministrativeRegionDistrictName(
+                        getAreaName(storeInfo.getAdministrativeRegionDistrictAdcode())
+                );
+            }
+
+            // 6. 执行更新
+            int updateNum = storeInfoMapper.updateById(storeInfo);
+
+            // 7. 更新地理位置信息(处理经纬度解析异常)
+            if (!StringUtils.isEmpty(longitude) && !StringUtils.isEmpty(latitude)) {
+                try {
+                    double lon = Double.parseDouble(longitude);
+                    double lat = Double.parseDouble(latitude);
+                    nearMeService.inGeolocation(new Point(lon, lat), storeInfo.getId().toString(), Boolean.TRUE);
+                } catch (NumberFormatException e) {
+                    log.error("经纬度解析失败:longitude=" + longitude + ", latitude=" + latitude);
+                    // 可选:根据业务是否抛出异常,或忽略
+                    // throw new IllegalArgumentException("经纬度格式错误", e);
+                }
+            }
+            return updateNum;
+        } else {
+            StoreInfo storeInfo = new StoreInfo();
+            BeanUtils.copyProperties(storeInfodto, storeInfo);
+            int num = storeInfoMapper.insert(storeInfo);
+            return num;
+        }
+    }
+
+    void verificationStoreInfoStatus(StoreInfoDto storeInfo) {
+        //营业状态 0:正常营业, 1:暂停营业, 2:筹建中, 99:永久关门
+        if (storeInfo.getBusinessStatus() == 99) {
+            //判断店铺有未完成的订单
+            List<LifeUserOrder> lifeUserOrders = lifeUserOrderMapper.selectList(new LambdaQueryWrapper<LifeUserOrder>().in(LifeUserOrder::getStatus, 0, 3).eq(LifeUserOrder::getStoreId, storeInfo.getId()).eq(LifeUserOrder::getDeleteFlag, 0));
+            if (!CollectionUtils.isEmpty(lifeUserOrders)) {
+                throw new RuntimeException("店铺有未完成的订单");
+            }
+            //判断店铺有正在售卖的商品
+            List<LifeGroupBuyMain> lifeGroupBuyMainList = lifeGroupBuyMainMapper.selectList(new LambdaQueryWrapper<LifeGroupBuyMain>().eq(LifeGroupBuyMain::getStoreId, storeInfo.getId()).eq(LifeGroupBuyMain::getStatus, 5).eq(LifeGroupBuyMain::getDeleteFlag, 0));
+            List<LifeCoupon> lifeCoupons = lifeCouponMapper.selectList(new LambdaQueryWrapper<LifeCoupon>().in(LifeCoupon::getStatus, 0, 1).and(qw -> qw.gt(LifeCoupon::getStockQty, 0).or().gt(LifeCoupon::getSingleQty, 0)).eq(LifeCoupon::getStoreId, storeInfo.getId()).eq(LifeCoupon::getType, 1).eq(LifeCoupon::getDeleteFlag, 0));
+            if (!CollectionUtils.isEmpty(lifeCoupons) || !CollectionUtils.isEmpty(lifeGroupBuyMainList)) {
+                throw new RuntimeException("店铺有正在销售的商品");
+            }
+        }
+    }
+
+    // 抽取:根据区域编码查询区域名称(通用方法)
+    private String getAreaName(String areaCode) {
+        if (StringUtils.isEmpty(areaCode)) {
+            return ""; // 或返回null,根据业务定义
+        }
+        EssentialCityCode cityCode = essentialCityCodeMapper.selectOne(
+                new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, areaCode)
+        );
+        return Objects.nonNull(cityCode) ? cityCode.getAreaName() : "";
+    }
+
+}

+ 22 - 0
alien-store/src/main/java/shop/alien/store/controller/AliController.java

@@ -27,6 +27,7 @@ import javax.servlet.http.HttpServletResponse;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Collectors;
 
@@ -312,4 +313,25 @@ public class AliController {
                 outTradeNo, refundAmount, refundReason, partialRefundCode);
         return aliApi.processRefund(outTradeNo, refundAmount, refundReason, partialRefundCode);
     }
+
+    /**
+     * 二手委托人识别(底层调用IDcard识别)
+     */
+    @ApiOperation("二手委托人识别(底层调用IDcard识别)")
+    @ApiOperationSupport(order = 16)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "imageUrl", value = "图片文件", dataType = "File", paramType = "query", required = true),
+            @ApiImplicitParam(name = "ocrType", value = "OCR识别类型", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userName", value = "委托人姓名", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "idCard", value = "委托人身份证号", dataType = "String", paramType = "query", required = true)
+    })
+    @PostMapping("/secondClient")
+    public R secondClient(@RequestBody Map<String, String> params)
+    {
+        try {
+            return aliApi.secondClient(params.get("imageUrl"), params.get("ocrType"), params.get("userName"), params.get("idCard"));
+        } catch (Exception e) {
+            return R.fail(e.getMessage());
+        }
+    }
 }

+ 5 - 1
alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponStoreFriendServiceImpl.java

@@ -367,9 +367,13 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
                 //存入接收人
                 lifeNotice.setReceiverId("user_" + lifeUser.getUserPhone());
                 //存入信息
-                lifeNotice.setContext(storeInfo.getStoreName() + " 赠送了您他的好友店铺优惠券,快去我的券包查看吧~");
+                String text = storeInfo.getStoreName() + "赠送了您他的好友店铺优惠券,快去我的券包查看吧~";
+                com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
+                jsonObject.put("message", text);
+                lifeNotice.setContext(jsonObject.toJSONString());
                 //存入类型
                 lifeNotice.setNoticeType(1);
+                lifeNotice.setTitle("赠劵通知");
                 lifeNoticeMapper.insert(lifeNotice);
 
             }

+ 75 - 0
alien-store/src/main/java/shop/alien/store/util/ali/AliApi.java

@@ -14,6 +14,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
 import shop.alien.entity.store.LifeUser;
 import shop.alien.entity.store.StoreAliPayErrorLog;
 import shop.alien.entity.store.StoreAliPayLog;
@@ -22,12 +24,15 @@ import shop.alien.store.service.LifeUserService;
 import shop.alien.store.service.StoreAliPayErrorLogService;
 import shop.alien.store.service.StoreAliPayLogService;
 import shop.alien.store.service.StoreUserService;
+import shop.alien.store.util.ali.ocr.OcrStrategy;
+import shop.alien.store.util.ali.ocr.OcrStrategyFactory;
 import shop.alien.util.common.RandomCreateUtil;
 import shop.alien.util.common.UniqueRandomNumGenerator;
 import shop.alien.util.common.UrlEncode;
 import shop.alien.util.system.OSUtil;
 
 import java.text.SimpleDateFormat;
+import java.util.Base64;
 import java.util.Date;
 
 /**
@@ -51,6 +56,8 @@ public class AliApi {
     private final StoreAliPayErrorLogService storeAliPayErrorLogService;
     
 
+    private final OcrStrategyFactory ocrStrategyFactory;
+
     /**
      * 商家端appId
      */
@@ -690,6 +697,46 @@ public class AliApi {
         return sdf.format(new Date()) + RandomCreateUtil.getRandomNum(4);
     }
 
+    /**
+     * 支付宝OCR识别
+     *
+     * @param imageId 图片id
+     * @param ocrType OCR识别类型
+     * @return OCR识别结果
+     * @throws Exception 识别异常
+     */
+    public R ocrRequest(String imageId, String ocrType) throws Exception {
+        OcrStrategy strategy = ocrStrategyFactory.getStrategy(ocrType);
+        return strategy.recognize(imageId);
+    }
+
+    /**
+     * 支付宝OCR识别
+     *
+     * @param imageUrl 图片url
+     * @param ocrType OCR识别类型
+     * @return OCR识别结果
+     * @throws Exception 识别异常
+     */
+    public R ocrRequestUrl(String imageUrl, String ocrType) throws Exception {
+        OcrStrategy strategy = ocrStrategyFactory.getStrategy(ocrType);
+        return strategy.recognizeUrl(imageUrl);
+    }
+
+    /**
+     * 支付宝OCR识别(Base64方式,使用策略工厂模式)
+     * 目前没用w
+     * @param imageFile 图片文件
+     * @param ocrType OCR识别类型
+     * @return OCR识别结果
+     * @throws Exception 识别异常
+     */
+    public R ocrRequestByBase64(MultipartFile imageFile, String ocrType) throws Exception {
+        OcrStrategy strategy = ocrStrategyFactory.getStrategy(ocrType);
+        byte[] imageBytes = imageFile.getBytes();
+        String imageBase64 = Base64.getEncoder().encodeToString(imageBytes);
+        return strategy.recognizeByBase64(imageBase64);
+    }
 
     private String getMassage(String subCode) {
         switch (subCode) {
@@ -730,4 +777,32 @@ public class AliApi {
         return "支付宝请求错误";
     }
 
+    public R secondClient(String imageUrl, String ocrType, String userName, String idCard) {
+        try {
+            R r = this.ocrRequestUrl(imageUrl, ocrType);
+            r.getCode();
+            if(200 == r.getCode()) {
+                if(!JSONObject.parseObject(r.getData().toString()).containsKey("face")) {
+                    return R.fail("OCR识别异常:请上传身份证正面照片");
+                }
+                JSONObject jsonObject = JSONObject.parseObject(r.getData().toString()).getJSONObject("face").getJSONObject("data");
+                String name = jsonObject.getString("name");
+                String idCardNo = jsonObject.getString("idNumber");
+                if(userName.equals(name) && idCard.equals(idCardNo)) {
+                    return R.success("验证成功");
+                } else {
+                    return R.fail("委托人姓名或身份证号错误");
+                }
+            }
+        } catch (IllegalArgumentException e) {
+            // 7. 细化异常类型(JSON 解析失败,比如数据格式不匹配)
+            log.error("OCR 结果解析失败(数据格式异常):{}", e.getMessage(), e);
+            return R.fail("OCR识别异常:返回数据格式错误");
+        } catch (Exception e) {
+            // 8. 兜底异常(记录详细日志,便于排查)
+            log.error("委托人身份验证未知异常:{}", e.getMessage(), e);
+            return R.fail("委托人身份验证未知异常:"+e.getMessage());
+        }
+        return R.fail("OCR识别异常");
+    }
 }

+ 95 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/AbstractOcrStrategy.java

@@ -0,0 +1,95 @@
+package shop.alien.store.util.ali.ocr;
+
+import com.aliyun.ocr_api20210707.Client;
+import com.aliyun.tea.TeaException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * OCR策略抽象基类
+ * 提供公共的客户端创建和异常处理逻辑
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+public abstract class AbstractOcrStrategy implements OcrStrategy {
+    
+    @Value("${ali.ocr.accessKeyId:}")
+    protected String accessKeyId;
+    
+    @Value("${ali.ocr.accessKeySecret:}")
+    protected String accessKeySecret;
+    
+    @Value("${ali.ocr.endpoint:ocr-api.cn-hangzhou.aliyuncs.com}")
+    protected String endpoint;
+    
+    /**
+     * 创建OCR客户端
+     * 
+     * @return OCR客户端
+     * @throws Exception 创建客户端异常
+     */
+    protected Client createOcrClient() throws Exception {
+        // 工程代码建议使用更安全的无 AK 方式,凭据配置方式请参见:https://help.aliyun.com/document_detail/378657.html。
+        com.aliyun.credentials.Client credential = new com.aliyun.credentials.Client();
+        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
+                .setCredential(credential);
+        
+        // 如果配置了AccessKey,则使用配置的值,否则使用默认值(仅用于测试)
+        if (org.apache.commons.lang3.StringUtils.isNotBlank(accessKeyId)) {
+            config.setAccessKeyId(accessKeyId);
+        } else {
+            // 默认值,仅用于测试,生产环境应该从配置文件读取
+            config.setAccessKeyId("LTAI5tLAUTQg7R1xaKvxAYJu");
+        }
+        
+        if (org.apache.commons.lang3.StringUtils.isNotBlank(accessKeySecret)) {
+            config.setAccessKeySecret(accessKeySecret);
+        } else {
+            // 默认值,仅用于测试,生产环境应该从配置文件读取
+            config.setAccessKeySecret("ayVk34nK9vQZ2bs5vDCYftQCEXXN3B");
+        }
+        
+        // Endpoint 请参考 https://api.aliyun.com/product/ocr-api
+        config.endpoint = endpoint;
+        
+        return new Client(config);
+    }
+    
+    /**
+     * 处理OCR识别异常
+     * 
+     * @param error 异常对象
+     * @param errorMessage 错误消息
+     * @throws Exception 重新抛出的异常
+     */
+    protected void handleOcrException(TeaException error, String errorMessage) throws Exception {
+        log.error("OCR识别失败: {}", errorMessage, error);
+        // 错误 message
+        log.error("错误信息: {}", error.getMessage());
+        // 诊断地址
+        if (error.getData() != null && error.getData().get("Recommend") != null) {
+            log.error("诊断地址: {}", error.getData().get("Recommend"));
+        }
+        com.aliyun.teautil.Common.assertAsString(error.message);
+        throw new Exception("OCR识别失败: " + errorMessage, error);
+    }
+    
+    /**
+     * 处理通用异常
+     * 
+     * @param error 异常对象
+     * @param errorMessage 错误消息
+     * @throws Exception 重新抛出的异常
+     */
+    protected void handleOcrException(Exception error, String errorMessage) throws Exception {
+        if (error instanceof TeaException) {
+            handleOcrException((TeaException) error, errorMessage);
+        } else {
+            TeaException teaException = new TeaException(error.getMessage(), error);
+            handleOcrException(teaException, errorMessage);
+        }
+    }
+}
+

+ 49 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/OcrStrategy.java

@@ -0,0 +1,49 @@
+package shop.alien.store.util.ali.ocr;
+
+import shop.alien.entity.result.R;
+
+/**
+ * OCR识别策略接口
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+public interface OcrStrategy {
+
+    /**
+     * 执行OCR识别
+     *
+     * @param imageId 图片id
+     * @return OCR识别结果(JSON格式)
+     * @throws Exception 识别异常
+     */
+    R recognize(String imageId) throws Exception;
+
+    /**
+     * 执行OCR识别
+     *
+     * @param imageUrl 图片url
+     * @return OCR识别结果(JSON格式)
+     * @throws Exception 识别异常
+     */
+    R recognizeUrl(String imageUrl) throws Exception;
+
+    /**
+     * 执行OCR识别(Base64方式)
+     *
+     * @param imageBase64 图片Base64编码
+     * @return OCR识别结果(JSON格式)
+     * @throws Exception 识别异常
+     */
+    R recognizeByBase64(String imageBase64) throws Exception;
+
+    /**
+     * 获取策略类型字符串
+     *
+     * @return 策略类型字符串
+     */
+    String getType();
+}
+
+
+

+ 79 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/OcrStrategyFactory.java

@@ -0,0 +1,79 @@
+package shop.alien.store.util.ali.ocr;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * OCR策略工厂
+ * 根据OCR类型创建对应的策略实例
+ * 
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+@Component
+public class OcrStrategyFactory {
+    
+    @Autowired
+    private List<OcrStrategy> ocrStrategies;
+    
+    private final Map<String, OcrStrategy> strategyMap = new HashMap<>();
+    
+    /**
+     * 初始化策略映射
+     */
+    @PostConstruct
+    public void init() {
+        if (ocrStrategies != null && !ocrStrategies.isEmpty()) {
+            for (OcrStrategy strategy : ocrStrategies) {
+                strategyMap.put(strategy.getType(), strategy);
+                log.info("注册OCR策略: {} -> {}", strategy.getType(), strategy.getClass().getSimpleName());
+            }
+        }
+    }
+    
+    /**
+     * 根据类型获取OCR策略
+     * 
+     * @param type OCR类型
+     * @return OCR策略实例
+     * @throws IllegalArgumentException 如果类型不存在
+     */
+    public OcrStrategy getStrategy(String type) {
+        OcrStrategy strategy = strategyMap.get(type);
+        if (strategy == null) {
+            throw new IllegalArgumentException("不支持的OCR类型: " + type);
+        }
+        return strategy;
+    }
+    
+    /**
+     * 根据类型代码获取OCR策略
+     * 
+     * @param typeCode 类型代码
+     * @return OCR策略实例
+     * @throws IllegalArgumentException 如果类型不存在
+     */
+    public OcrStrategy getStrategyByCode(String typeCode) {
+        return getStrategy(typeCode);
+    }
+    
+    /**
+     * 检查是否支持指定的OCR类型
+     * 
+     * @param type OCR类型
+     * @return 是否支持
+     */
+    public boolean supports(String type) {
+        return strategyMap.containsKey(type);
+    }
+}
+
+
+

+ 115 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/BusinessLicenseOcrStrategy.java

@@ -0,0 +1,115 @@
+package shop.alien.store.util.ali.ocr.strategy;
+
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.ocr_api20210707.Client;
+import com.aliyun.ocr_api20210707.models.RecognizeBusinessLicenseRequest;
+import com.aliyun.ocr_api20210707.models.RecognizeBusinessLicenseResponse;
+import com.aliyun.ocr_api20210707.models.RecognizeBusinessLicenseResponseBody;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teautil.models.RuntimeOptions;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.store.service.StoreImgService;
+import shop.alien.store.util.ali.ocr.AbstractOcrStrategy;
+import shop.alien.util.common.constant.OcrTypeEnum;
+
+/**
+ * 营业执照OCR识别策略
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class BusinessLicenseOcrStrategy extends AbstractOcrStrategy {
+
+    private final StoreImgService storeImgService;
+
+    @Override
+    public R recognize(String imageId) throws Exception {
+
+        // 从数据库中获取图片URL
+        StoreImg storeImage = storeImgService.getById(imageId);
+        String imageUrl = storeImage.getImgUrl();
+        return recognizeUrl(imageUrl);
+    }
+
+    @Override
+    public R recognizeUrl(String imageUrl) throws Exception {
+        Client client = createOcrClient();
+        RecognizeBusinessLicenseRequest request = new RecognizeBusinessLicenseRequest()
+                .setUrl(imageUrl);
+
+        try {
+            RecognizeBusinessLicenseResponse response = client.recognizeBusinessLicenseWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeBusinessLicenseResponseBody body = response.getBody();
+
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("营业执照OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+
+        } catch (TeaException error) {
+            handleOcrException(error, "营业执照识别失败");
+            return R.fail("营业执照识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "营业执照识别异常");
+            return R.fail("营业执照识别异常");
+        }
+    }
+
+    @Override
+    public R recognizeByBase64(String imageBase64) throws Exception {
+        Client client = createOcrClient();
+        // 将Base64字符串转换为字节数组,然后创建InputStream
+        byte[] imageBytes = java.util.Base64.getDecoder().decode(imageBase64);
+        java.io.InputStream imageInputStream = new java.io.ByteArrayInputStream(imageBytes);
+
+        RecognizeBusinessLicenseRequest request = new RecognizeBusinessLicenseRequest()
+                .setBody(imageInputStream);
+
+        try {
+            RecognizeBusinessLicenseResponse response = client.recognizeBusinessLicenseWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeBusinessLicenseResponseBody body = response.getBody();
+
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("营业执照OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+
+        } catch (TeaException error) {
+            handleOcrException(error, "营业执照识别失败");
+            return R.fail("营业执照识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "营业执照识别异常");
+            return R.fail("营业执照识别异常");
+        } finally {
+            // 关闭流
+            if (imageInputStream != null) {
+                try {
+                    imageInputStream.close();
+                } catch (java.io.IOException e) {
+                    log.warn("关闭输入流失败", e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getType() {
+        return OcrTypeEnum.BUSINESS_LICENSE.getCode();
+    }
+}
+

+ 111 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/FoodManageLicenseOcrStrategy.java

@@ -0,0 +1,111 @@
+package shop.alien.store.util.ali.ocr.strategy;
+
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.ocr_api20210707.Client;
+import com.aliyun.ocr_api20210707.models.*;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teautil.models.RuntimeOptions;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.store.service.StoreImgService;
+import shop.alien.store.util.ali.ocr.AbstractOcrStrategy;
+import shop.alien.util.common.constant.OcrTypeEnum;
+
+/**
+ * 食品经营许可证OCR识别策略
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class FoodManageLicenseOcrStrategy extends AbstractOcrStrategy {
+
+    private final StoreImgService storeImgService;
+
+    @Override
+    public R recognize(String imageId) throws Exception {
+
+        // 从数据库中获取图片URL
+        StoreImg storeImage = storeImgService.getById(imageId);
+        String imageUrl = storeImage.getImgUrl();
+        return recognizeUrl(imageUrl);
+    }
+
+    @Override
+    public R recognizeUrl(String imageUrl) throws Exception {
+        Client client = createOcrClient();
+        RecognizeFoodManageLicenseRequest request = new RecognizeFoodManageLicenseRequest()
+                .setUrl(imageUrl);
+
+        try {
+            RecognizeFoodManageLicenseResponse response = client.recognizeFoodManageLicenseWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeFoodManageLicenseResponseBody body = response.getBody();
+
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("食品经营许可证OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+
+        } catch (TeaException error) {
+            handleOcrException(error, "食品经营许可证识别失败");
+            return R.fail("食品经营许可证识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "食品经营许可证识别异常");
+            return R.fail("食品经营许可证识别异常");
+        }
+    }
+
+    @Override
+    public R recognizeByBase64(String imageBase64) throws Exception {
+        Client client = createOcrClient();
+        // 将Base64字符串转换为字节数组,然后创建InputStream
+        byte[] imageBytes = java.util.Base64.getDecoder().decode(imageBase64);
+        java.io.InputStream imageInputStream = new java.io.ByteArrayInputStream(imageBytes);
+
+        RecognizeFoodManageLicenseRequest request = new RecognizeFoodManageLicenseRequest()
+                .setBody(imageInputStream);
+
+        try {
+            RecognizeFoodManageLicenseResponse response = client.recognizeFoodManageLicenseWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeFoodManageLicenseResponseBody body = response.getBody();
+
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("食品经营许可证OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+        } catch (TeaException error) {
+            handleOcrException(error, "食品经营许可证识别失败");
+            return R.fail("食品经营许可证识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "食品经营许可证识别异常");
+            return R.fail("食品经营许可证识别异常");
+        } finally {
+            // 关闭流
+            if (imageInputStream != null) {
+                try {
+                    imageInputStream.close();
+                } catch (java.io.IOException e) {
+                    log.warn("关闭输入流失败", e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getType() {
+        return OcrTypeEnum.FOOD_MANAGE_LICENSE.getCode();
+    }
+}

+ 113 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/IdCardOcrStrategy.java

@@ -0,0 +1,113 @@
+package shop.alien.store.util.ali.ocr.strategy;
+
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.ocr_api20210707.Client;
+import com.aliyun.ocr_api20210707.models.*;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teautil.models.RuntimeOptions;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.store.service.StoreImgService;
+import shop.alien.store.util.ali.ocr.AbstractOcrStrategy;
+import shop.alien.util.common.constant.OcrTypeEnum;
+
+/**
+ * 身份证OCR识别策略
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class IdCardOcrStrategy extends AbstractOcrStrategy {
+
+    private final StoreImgService storeImgService;
+
+    @Override
+    public R recognize(String imageId) throws Exception {
+
+        // 从数据库中获取图片URL
+        StoreImg storeImage = storeImgService.getById(imageId);
+        String imageUrl = storeImage.getImgUrl();
+        return recognizeUrl(imageUrl);
+    }
+
+    @Override
+    public R recognizeUrl(String imageUrl) throws Exception {
+        Client client = createOcrClient();
+        RecognizeIdcardRequest request = new RecognizeIdcardRequest()
+                .setUrl(imageUrl); // 默认识别正面,可根据需要修改为 "back" 识别反面
+
+        try {
+            RecognizeIdcardResponse response = client.recognizeIdcardWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeIdcardResponseBody body = response.getBody();
+
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("身份证OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+
+        } catch (TeaException error) {
+            handleOcrException(error, "身份证识别失败");
+            return R.fail("身份证识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "身份证识别异常");
+            return R.fail("身份证识别异常");
+        }
+    }
+
+    @Override
+    public R recognizeByBase64(String imageBase64) throws Exception {
+        Client client = createOcrClient();
+        // 将Base64字符串转换为字节数组,然后创建InputStream
+        byte[] imageBytes = java.util.Base64.getDecoder().decode(imageBase64);
+        java.io.InputStream imageInputStream = new java.io.ByteArrayInputStream(imageBytes);
+
+        RecognizeIdcardRequest request = new RecognizeIdcardRequest()
+                .setBody(imageInputStream); // 默认识别正面,可根据需要修改为 "back" 识别反面
+
+        try {
+            RecognizeIdcardResponse response = client.recognizeIdcardWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeIdcardResponseBody body = response.getBody();
+
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("身份证OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+
+        } catch (TeaException error) {
+            handleOcrException(error, "身份证识别失败");
+            return R.fail("身份证识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "身份证识别异常");
+            return R.fail("身份证识别异常");
+        } finally {
+            // 关闭流
+            if (imageInputStream != null) {
+                try {
+                    imageInputStream.close();
+                } catch (java.io.IOException e) {
+                    log.warn("关闭输入流失败", e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getType() {
+        return OcrTypeEnum.ID_CARD.getCode();
+    }
+}
+

+ 7 - 0
alien-util/pom.xml

@@ -130,6 +130,13 @@
             <version>2.20.0</version>
         </dependency>
 
+        <!-- 阿里云OCR文字识别 -->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>ocr_api20210707</artifactId>
+            <version>3.1.3</version>
+        </dependency>
+
         <!--openoffice-->
         <dependency>
             <groupId>com.artofsolving</groupId>

+ 50 - 0
alien-util/src/main/java/shop/alien/util/common/constant/OcrTypeEnum.java

@@ -0,0 +1,50 @@
+package shop.alien.util.common.constant;
+
+/**
+ * OCR识别类型枚举
+ * 
+ * @author ssk
+ * @date 2024/12/10
+ */
+public enum OcrTypeEnum {
+    /**
+     * 营业执照识别
+     */
+    BUSINESS_LICENSE("BUSINESS_LICENSE", "营业执照识别"),
+    
+    /**
+     * 身份证识别
+     */
+    ID_CARD("ID_CARD", "身份证识别"),
+    /**
+     * 食品管理许可证识别
+     */
+    FOOD_MANAGE_LICENSE("FOOD_MANAGE_LICENSE", "食品经营许可证识别"),
+
+    /**
+     * 银行卡识别
+     */
+    BANK_CARD("BANK_CARD", "银行卡识别"),
+    
+    /**
+     * 通用文字识别
+     */
+    GENERAL("GENERAL", "通用文字识别");
+    
+    private final String code;
+    private final String description;
+    
+    OcrTypeEnum(String code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+    
+    public String getCode() {
+        return code;
+    }
+    
+    public String getDescription() {
+        return description;
+    }
+}
+