diff --git a/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCode.java b/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCode.java
index 51c4b4e..0400ada 100644
--- a/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCode.java
+++ b/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCode.java
@@ -209,6 +209,11 @@ public enum ErrorCode implements ErrorCodeInterface {
* 余额金额无效
*/
BALANCE_AMOUNT_INVALID(11006, "余额金额无效", "Business.BALANCE_AMOUNT_INVALID"),
+
+ /**
+ * 该值不允许为空
+ */
+ VALUE_NOT_ALLOWED(11007, "该值不允许为空", "Business.VALUE_NOT_ALLOWED"),
;
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/UserBalanceLogApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/UserBalanceLogApplicationService.java
new file mode 100644
index 0000000..ab72963
--- /dev/null
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/UserBalanceLogApplicationService.java
@@ -0,0 +1,249 @@
+package com.agileboot.domain.ab98.user_balance_log;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import com.agileboot.common.core.page.PageDTO;
+import com.agileboot.domain.ab98.user_balance_log.command.AddUserBalanceLogCommand;
+import com.agileboot.domain.ab98.user_balance_log.command.UpdateUserBalanceLogCommand;
+import com.agileboot.domain.ab98.user_balance_log.db.UserBalanceLogEntity;
+import com.agileboot.domain.ab98.user_balance_log.db.UserBalanceLogService;
+import com.agileboot.domain.ab98.user_balance_log.dto.UserBalanceLogDTO;
+import com.agileboot.domain.ab98.user_balance_log.model.UserBalanceLogModel;
+import com.agileboot.domain.ab98.user_balance_log.model.UserBalanceLogModelFactory;
+import com.agileboot.domain.ab98.user_balance_log.query.SearchUserBalanceLogQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * 用户余额变更日志应用服务
+ *
+ *
+ * @author valarchie
+ * @since 2025-12-06
+ */
+@Service
+@RequiredArgsConstructor
+public class UserBalanceLogApplicationService {
+
+ private final UserBalanceLogService userBalanceLogService;
+ private final UserBalanceLogModelFactory userBalanceLogModelFactory;
+
+ /**
+ * 新增用户余额变更日志
+ *
+ * @param command 新增命令
+ * @return 用户余额变更日志DTO
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public UserBalanceLogDTO add(AddUserBalanceLogCommand command) {
+ UserBalanceLogModel model = userBalanceLogModelFactory.create(command);
+ boolean success = model.save();
+ if (!success) {
+ throw new RuntimeException("新增用户余额变更日志失败");
+ }
+ return new UserBalanceLogDTO(model);
+ }
+
+ /**
+ * 更新用户余额变更日志
+ *
+ * @param command 更新命令
+ * @return 用户余额变更日志DTO
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public UserBalanceLogDTO update(UpdateUserBalanceLogCommand command) {
+ UserBalanceLogModel model = userBalanceLogModelFactory.create(command);
+ boolean success = model.update();
+ if (!success) {
+ throw new RuntimeException("更新用户余额变更日志失败");
+ }
+ return new UserBalanceLogDTO(model);
+ }
+
+ /**
+ * 根据ID删除用户余额变更日志
+ *
+ * @param logId 主键ID
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public void delete(Long logId) {
+ if (logId == null) {
+ throw new IllegalArgumentException("日志ID不能为空");
+ }
+
+ boolean success = userBalanceLogService.removeById(logId);
+ if (!success) {
+ throw new RuntimeException("删除用户余额变更日志失败");
+ }
+ }
+
+ /**
+ * 根据ID查询用户余额变更日志
+ *
+ * @param logId 主键ID
+ * @return 用户余额变更日志DTO
+ */
+ public UserBalanceLogDTO getById(Long logId) {
+ if (logId == null) {
+ return null;
+ }
+
+ UserBalanceLogEntity entity = userBalanceLogService.getById(logId);
+ return entity != null ? new UserBalanceLogDTO(entity) : null;
+ }
+
+ /**
+ * 查询用户余额变更日志列表
+ *
+ * @param query 查询参数
+ * @return 用户余额变更日志分页DTO
+ */
+ public PageDTO getList(SearchUserBalanceLogQuery query) {
+ // 构建查询条件
+ Page page = query.toPage();
+ UserBalanceLogEntity entity = new UserBalanceLogEntity();
+ BeanUtil.copyProperties(query, entity);
+
+ // 执行查询
+ List entityList = userBalanceLogService.page(page, query.addQueryCondition()).getRecords();
+
+ // 转换为DTO
+ List dtoList = entityList.stream()
+ .map(UserBalanceLogDTO::new)
+ .collect(Collectors.toList());
+
+ // 获取总数
+ Long total = page.getTotal();
+
+ return new PageDTO<>(dtoList, total);
+ }
+
+ /**
+ * 批量删除用户余额变更日志
+ *
+ * @param logIds 主键ID列表
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public void batchDelete(List logIds) {
+ if (CollUtil.isEmpty(logIds)) {
+ throw new IllegalArgumentException("日志ID列表不能为空");
+ }
+
+ boolean success = userBalanceLogService.removeByIds(logIds);
+ if (!success) {
+ throw new RuntimeException("批量删除用户余额变更日志失败");
+ }
+ }
+
+ /**
+ * 根据用户余额ID查询变更日志列表
+ *
+ * @param userBalanceId 用户余额ID
+ * @return 用户余额变更日志DTO列表
+ */
+ public List getByUserBalanceId(Long userBalanceId) {
+ if (userBalanceId == null) {
+ return null;
+ }
+
+ List entityList = userBalanceLogService.getByUserBalanceId(userBalanceId);
+ if (CollUtil.isEmpty(entityList)) {
+ return null;
+ }
+
+ return entityList.stream()
+ .map(UserBalanceLogDTO::new)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 根据订单ID查询变更日志列表
+ *
+ * @param orderId 订单ID
+ * @return 用户余额变更日志DTO列表
+ */
+ public List getByOrderId(Long orderId) {
+ if (orderId == null) {
+ return null;
+ }
+
+ List entityList = userBalanceLogService.getByOrderId(orderId);
+ if (CollUtil.isEmpty(entityList)) {
+ return null;
+ }
+
+ return entityList.stream()
+ .map(UserBalanceLogDTO::new)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 根据审批ID查询变更日志列表
+ *
+ * @param approvalId 审批ID
+ * @return 用户余额变更日志DTO列表
+ */
+ public List getByApprovalId(Long approvalId) {
+ if (approvalId == null) {
+ return null;
+ }
+
+ List entityList = userBalanceLogService.getByApprovalId(approvalId);
+ if (CollUtil.isEmpty(entityList)) {
+ return null;
+ }
+
+ return entityList.stream()
+ .map(UserBalanceLogDTO::new)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 根据订单商品ID查询变更日志列表
+ *
+ * @param orderGoodsId 订单商品ID
+ * @return 用户余额变更日志DTO列表
+ */
+ public List getByOrderGoodsId(Long orderGoodsId) {
+ if (orderGoodsId == null) {
+ return null;
+ }
+
+ List entityList = userBalanceLogService.getByOrderGoodsId(orderGoodsId);
+ if (CollUtil.isEmpty(entityList)) {
+ return null;
+ }
+
+ return entityList.stream()
+ .map(UserBalanceLogDTO::new)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 根据变更类型查询变更日志列表
+ *
+ * @param changeType 变更类型
+ * @return 用户余额变更日志DTO列表
+ */
+ public List getByChangeType(Integer changeType) {
+ if (changeType == null) {
+ return null;
+ }
+
+ List entityList = userBalanceLogService.getByChangeType(changeType);
+ if (CollUtil.isEmpty(entityList)) {
+ return null;
+ }
+
+ return entityList.stream()
+ .map(UserBalanceLogDTO::new)
+ .collect(Collectors.toList());
+ }
+
+}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/command/AddUserBalanceLogCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/command/AddUserBalanceLogCommand.java
new file mode 100644
index 0000000..ca09e36
--- /dev/null
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/command/AddUserBalanceLogCommand.java
@@ -0,0 +1,45 @@
+package com.agileboot.domain.ab98.user_balance_log.command;
+
+import com.agileboot.domain.ab98.user_balance_log.db.UserBalanceLogEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ *
+ * 新增用户余额变更日志命令
+ *
+ *
+ * @author valarchie
+ * @since 2025-12-06
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class AddUserBalanceLogCommand extends UserBalanceLogEntity {
+
+ @NotNull(message = "用户余额ID不能为空")
+ private Long userBalanceId;
+
+ @NotNull(message = "变更类型不能为空")
+ private Integer changeType;
+
+ @NotNull(message = "变更金额不能为空")
+ private Long changeAmount;
+
+ @NotNull(message = "变更前已用余额不能为空")
+ private Long useBalanceBefore;
+
+ @NotNull(message = "变更后已用余额不能为空")
+ private Long useBalanceAfter;
+
+ private Long orderId;
+
+ private Long approvalId;
+
+ private Long orderGoodsId;
+
+ @NotNull(message = "创建者ID不能为空")
+ private Long creatorId;
+
+}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/command/UpdateUserBalanceLogCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/command/UpdateUserBalanceLogCommand.java
new file mode 100644
index 0000000..a0145e6
--- /dev/null
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/command/UpdateUserBalanceLogCommand.java
@@ -0,0 +1,24 @@
+package com.agileboot.domain.ab98.user_balance_log.command;
+
+import com.agileboot.domain.ab98.user_balance_log.db.UserBalanceLogEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ *
+ * 更新用户余额变更日志命令
+ *
+ *
+ * @author valarchie
+ * @since 2025-12-06
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class UpdateUserBalanceLogCommand extends AddUserBalanceLogCommand {
+
+ @NotNull(message = "日志ID不能为空")
+ private Long logId;
+
+}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/db/UserBalanceLogEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/db/UserBalanceLogEntity.java
new file mode 100644
index 0000000..040c5ca
--- /dev/null
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/db/UserBalanceLogEntity.java
@@ -0,0 +1,79 @@
+package com.agileboot.domain.ab98.user_balance_log.db;
+
+import com.agileboot.common.core.base.BaseEntity;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ *
+ * 用户余额变更日志表
+ *
+ *
+ * @author valarchie
+ * @since 2025-12-06
+ */
+@Getter
+@Setter
+@TableName("user_balance_log")
+@ApiModel(value = "UserBalanceLogEntity对象", description = "用户余额变更日志表")
+public class UserBalanceLogEntity extends BaseEntity {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty("主键ID")
+ @TableId(value = "log_id", type = IdType.AUTO)
+ private Long logId;
+
+ @ApiModelProperty("用户余额ID")
+ @TableField("user_balance_id")
+ private Long userBalanceId;
+
+ @ApiModelProperty("变更类型(1-消费 2-审批归还 3-系统调整)")
+ @TableField("change_type")
+ private Integer changeType;
+
+ @ApiModelProperty("变更金额(单位:分,正数表示增加,负数表示减少)")
+ @TableField("change_amount")
+ private Long changeAmount;
+
+ @ApiModelProperty("变更前已用余额")
+ @TableField("use_balance_before")
+ private Long useBalanceBefore;
+
+ @ApiModelProperty("变更后已用余额")
+ @TableField("use_balance_after")
+ private Long useBalanceAfter;
+
+ @ApiModelProperty("关联订单ID")
+ @TableField("order_id")
+ private Long orderId;
+
+ @ApiModelProperty("关联审批ID")
+ @TableField("approval_id")
+ private Long approvalId;
+
+ @ApiModelProperty("关联订单商品ID")
+ @TableField("order_goods_id")
+ private Long orderGoodsId;
+
+ @ApiModelProperty("创建者ID")
+ @TableField("creator_id")
+ private Long creatorId;
+
+ @ApiModelProperty("更新者ID")
+ @TableField("updater_id")
+ private Long updaterId;
+
+
+ @Override
+ public Serializable pkVal() {
+ return this.logId;
+ }
+}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/db/UserBalanceLogMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/db/UserBalanceLogMapper.java
new file mode 100644
index 0000000..a3902a8
--- /dev/null
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/db/UserBalanceLogMapper.java
@@ -0,0 +1,48 @@
+package com.agileboot.domain.ab98.user_balance_log.db;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ *
+ * 用户余额变更日志表 Mapper 接口
+ *
+ *
+ * @author valarchie
+ * @since 2025-12-06
+ */
+@Mapper
+public interface UserBalanceLogMapper extends BaseMapper {
+
+ /**
+ * 根据用户余额ID查询变更日志列表
+ *
+ * @param userBalanceId 用户余额ID
+ * @return 变更日志列表
+ */
+ @Select("SELECT * FROM user_balance_log WHERE user_balance_id = #{userBalanceId} ORDER BY create_time DESC")
+ List selectByUserBalanceId(@Param("userBalanceId") Long userBalanceId);
+
+ /**
+ * 根据订单ID查询变更日志列表
+ *
+ * @param orderId 订单ID
+ * @return 变更日志列表
+ */
+ @Select("SELECT * FROM user_balance_log WHERE order_id = #{orderId} ORDER BY create_time DESC")
+ List selectByOrderId(@Param("orderId") Long orderId);
+
+ /**
+ * 根据审批ID查询变更日志列表
+ *
+ * @param approvalId 审批ID
+ * @return 变更日志列表
+ */
+ @Select("SELECT * FROM user_balance_log WHERE approval_id = #{approvalId} ORDER BY create_time DESC")
+ List selectByApprovalId(@Param("approvalId") Long approvalId);
+
+}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/db/UserBalanceLogService.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/db/UserBalanceLogService.java
new file mode 100644
index 0000000..0b105e9
--- /dev/null
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/db/UserBalanceLogService.java
@@ -0,0 +1,57 @@
+package com.agileboot.domain.ab98.user_balance_log.db;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ *
+ * 用户余额变更日志表 服务类
+ *
+ *
+ * @author valarchie
+ * @since 2025-12-06
+ */
+public interface UserBalanceLogService extends IService {
+
+ /**
+ * 根据用户余额ID查询变更日志列表
+ *
+ * @param userBalanceId 用户余额ID
+ * @return 变更日志列表
+ */
+ List getByUserBalanceId(Long userBalanceId);
+
+ /**
+ * 根据订单ID查询变更日志列表
+ *
+ * @param orderId 订单ID
+ * @return 变更日志列表
+ */
+ List getByOrderId(Long orderId);
+
+ /**
+ * 根据审批ID查询变更日志列表
+ *
+ * @param approvalId 审批ID
+ * @return 变更日志列表
+ */
+ List getByApprovalId(Long approvalId);
+
+ /**
+ * 根据订单商品ID查询变更日志列表
+ *
+ * @param orderGoodsId 订单商品ID
+ * @return 变更日志列表
+ */
+ List getByOrderGoodsId(Long orderGoodsId);
+
+ /**
+ * 根据变更类型查询变更日志列表
+ *
+ * @param changeType 变更类型
+ * @return 变更日志列表
+ */
+ List getByChangeType(Integer changeType);
+
+}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/db/UserBalanceLogServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/db/UserBalanceLogServiceImpl.java
new file mode 100644
index 0000000..4b71dab
--- /dev/null
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/db/UserBalanceLogServiceImpl.java
@@ -0,0 +1,95 @@
+package com.agileboot.domain.ab98.user_balance_log.db;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ *
+ * 用户余额变更日志表 服务实现类
+ *
+ *
+ * @author valarchie
+ * @since 2025-12-06
+ */
+@Service
+public class UserBalanceLogServiceImpl extends ServiceImpl implements UserBalanceLogService {
+
+ /**
+ * 根据用户余额ID查询变更日志列表
+ *
+ * @param userBalanceId 用户余额ID
+ * @return 变更日志列表
+ */
+ @Override
+ public List getByUserBalanceId(Long userBalanceId) {
+ if (userBalanceId == null) {
+ return null;
+ }
+ return baseMapper.selectByUserBalanceId(userBalanceId);
+ }
+
+ /**
+ * 根据订单ID查询变更日志列表
+ *
+ * @param orderId 订单ID
+ * @return 变更日志列表
+ */
+ @Override
+ public List getByOrderId(Long orderId) {
+ if (orderId == null) {
+ return null;
+ }
+ return baseMapper.selectByOrderId(orderId);
+ }
+
+ /**
+ * 根据审批ID查询变更日志列表
+ *
+ * @param approvalId 审批ID
+ * @return 变更日志列表
+ */
+ @Override
+ public List getByApprovalId(Long approvalId) {
+ if (approvalId == null) {
+ return null;
+ }
+ return baseMapper.selectByApprovalId(approvalId);
+ }
+
+ /**
+ * 根据订单商品ID查询变更日志列表
+ *
+ * @param orderGoodsId 订单商品ID
+ * @return 变更日志列表
+ */
+ @Override
+ public List getByOrderGoodsId(Long orderGoodsId) {
+ if (orderGoodsId == null) {
+ return null;
+ }
+ return lambdaQuery()
+ .eq(UserBalanceLogEntity::getOrderGoodsId, orderGoodsId)
+ .orderByDesc(UserBalanceLogEntity::getCreateTime)
+ .list();
+ }
+
+ /**
+ * 根据变更类型查询变更日志列表
+ *
+ * @param changeType 变更类型
+ * @return 变更日志列表
+ */
+ @Override
+ public List getByChangeType(Integer changeType) {
+ if (changeType == null) {
+ return null;
+ }
+ return lambdaQuery()
+ .eq(UserBalanceLogEntity::getChangeType, changeType)
+ .orderByDesc(UserBalanceLogEntity::getCreateTime)
+ .list();
+ }
+
+}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/dto/UserBalanceLogDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/dto/UserBalanceLogDTO.java
new file mode 100644
index 0000000..f553f6b
--- /dev/null
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/dto/UserBalanceLogDTO.java
@@ -0,0 +1,71 @@
+package com.agileboot.domain.ab98.user_balance_log.dto;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.agileboot.common.annotation.ExcelColumn;
+import com.agileboot.common.annotation.ExcelSheet;
+import com.agileboot.domain.ab98.user_balance_log.db.UserBalanceLogEntity;
+import lombok.Data;
+
+/**
+ *
+ * 用户余额变更日志DTO
+ *
+ *
+ * @author valarchie
+ * @since 2025-12-06
+ */
+@ExcelSheet(name = "用户余额变更日志")
+@Data
+public class UserBalanceLogDTO {
+
+ public UserBalanceLogDTO() {}
+
+ public UserBalanceLogDTO(UserBalanceLogEntity entity) {
+ if (entity != null) {
+ BeanUtil.copyProperties(entity, this);
+ }
+ }
+
+ @ExcelColumn(name = "主键ID")
+ private Long logId;
+
+ @ExcelColumn(name = "用户余额ID")
+ private Long userBalanceId;
+
+ @ExcelColumn(name = "变更类型(1-消费,2-审批归还,3-系统调整)")
+ private Integer changeType;
+
+ @ExcelColumn(name = "变更金额(分)")
+ private Long changeAmount;
+
+ @ExcelColumn(name = "变更前已用余额(分)")
+ private Long useBalanceBefore;
+
+ @ExcelColumn(name = "变更后已用余额(分)")
+ private Long useBalanceAfter;
+
+ @ExcelColumn(name = "关联订单ID")
+ private Long orderId;
+
+ @ExcelColumn(name = "关联审批ID")
+ private Long approvalId;
+
+ @ExcelColumn(name = "关联订单商品ID")
+ private Long orderGoodsId;
+
+ @ExcelColumn(name = "创建者ID")
+ private Long creatorId;
+
+ @ExcelColumn(name = "创建时间")
+ private String createTime;
+
+ @ExcelColumn(name = "更新者ID")
+ private Long updaterId;
+
+ @ExcelColumn(name = "更新时间")
+ private String updateTime;
+
+ @ExcelColumn(name = "删除标志")
+ private Boolean deleted;
+
+}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/model/UserBalanceLogModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/model/UserBalanceLogModel.java
new file mode 100644
index 0000000..55df430
--- /dev/null
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/model/UserBalanceLogModel.java
@@ -0,0 +1,133 @@
+package com.agileboot.domain.ab98.user_balance_log.model;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.agileboot.common.exception.ApiException;
+import com.agileboot.common.exception.error.ErrorCode;
+import com.agileboot.domain.ab98.user_balance_log.command.AddUserBalanceLogCommand;
+import com.agileboot.domain.ab98.user_balance_log.command.UpdateUserBalanceLogCommand;
+import com.agileboot.domain.ab98.user_balance_log.db.UserBalanceLogEntity;
+import com.agileboot.domain.ab98.user_balance_log.db.UserBalanceLogService;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ *
+ * 用户余额变更日志领域模型
+ *
+ *
+ * @author valarchie
+ * @since 2025-12-06
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class UserBalanceLogModel extends UserBalanceLogEntity {
+
+ private UserBalanceLogService userBalanceLogService;
+
+ public UserBalanceLogModel() {}
+
+ public UserBalanceLogModel(UserBalanceLogEntity entity, UserBalanceLogService userBalanceLogService) {
+ this.userBalanceLogService = userBalanceLogService;
+ if (entity != null) {
+ BeanUtil.copyProperties(entity, this);
+ }
+ }
+
+ public UserBalanceLogModel(UserBalanceLogService userBalanceLogService) {
+ this.userBalanceLogService = userBalanceLogService;
+ }
+
+ /**
+ * 从新增命令加载数据
+ *
+ * @param command 新增命令
+ */
+ public void loadAddCommand(AddUserBalanceLogCommand command) {
+ if (command != null) {
+ BeanUtil.copyProperties(command, this, "logId");
+ }
+ }
+
+ /**
+ * 从更新命令加载数据
+ *
+ * @param command 更新命令
+ */
+ public void loadUpdateCommand(UpdateUserBalanceLogCommand command) {
+ if (command != null) {
+ loadAddCommand(command);
+ if (command.getLogId() != null) {
+ this.setLogId(command.getLogId());
+ }
+ }
+ }
+
+ /**
+ * 验证变更类型是否有效
+ *
+ * @param changeType 变更类型
+ * @throws ApiException 无效变更类型异常
+ */
+ public void validateChangeType(Integer changeType) {
+ if (changeType == null || changeType < 1 || changeType > 3) {
+ throw new ApiException(ErrorCode.Business.VALUE_NOT_ALLOWED, "变更类型", "1-消费, 2-审批归还, 3-系统调整");
+ }
+ }
+
+ /**
+ * 验证变更金额是否有效
+ *
+ * @param changeAmount 变更金额
+ * @throws ApiException 无效变更金额异常
+ */
+ public void validateChangeAmount(Long changeAmount) {
+ if (changeAmount == null) {
+ throw new ApiException(ErrorCode.Business.VALUE_NOT_ALLOWED, "变更金额", "不能为空");
+ }
+ }
+
+ /**
+ * 验证变更前后的已用余额
+ *
+ * @param useBalanceBefore 变更前已用余额
+ * @param useBalanceAfter 变更后已用余额
+ * @throws ApiException 已用余额异常
+ */
+ public void validateUseBalance(Long useBalanceBefore, Long useBalanceAfter) {
+ if (useBalanceBefore == null || useBalanceBefore < 0) {
+ throw new ApiException(ErrorCode.Business.VALUE_NOT_ALLOWED, "变更前已用余额", "不能为空且不能为负数");
+ }
+
+ if (useBalanceAfter == null || useBalanceAfter < 0) {
+ throw new ApiException(ErrorCode.Business.VALUE_NOT_ALLOWED, "变更后已用余额", "不能为空且不能为负数");
+ }
+ }
+
+ /**
+ * 保存到数据库
+ *
+ * @return 是否成功
+ */
+ public boolean save() {
+ return userBalanceLogService.save(this);
+ }
+
+ /**
+ * 更新到数据库
+ *
+ * @return 是否成功
+ */
+ public boolean update() {
+ return userBalanceLogService.updateById(this);
+ }
+
+ /**
+ * 从数据库删除
+ *
+ * @return 是否成功
+ */
+ public boolean remove() {
+ return userBalanceLogService.removeById(this.getLogId());
+ }
+
+}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/model/UserBalanceLogModelFactory.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/model/UserBalanceLogModelFactory.java
new file mode 100644
index 0000000..b3b741c
--- /dev/null
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/model/UserBalanceLogModelFactory.java
@@ -0,0 +1,67 @@
+package com.agileboot.domain.ab98.user_balance_log.model;
+
+import com.agileboot.domain.ab98.user_balance_log.command.AddUserBalanceLogCommand;
+import com.agileboot.domain.ab98.user_balance_log.command.UpdateUserBalanceLogCommand;
+import com.agileboot.domain.ab98.user_balance_log.db.UserBalanceLogEntity;
+import com.agileboot.domain.ab98.user_balance_log.db.UserBalanceLogService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ * 用户余额变更日志领域模型工厂
+ *
+ *
+ * @author valarchie
+ * @since 2025-12-06
+ */
+@Component
+@RequiredArgsConstructor
+public class UserBalanceLogModelFactory {
+
+ private final UserBalanceLogService userBalanceLogService;
+
+ /**
+ * 创建空的用户余额变更日志模型
+ *
+ * @return 用户余额变更日志模型
+ */
+ public UserBalanceLogModel create() {
+ return new UserBalanceLogModel(userBalanceLogService);
+ }
+
+ /**
+ * 根据新增命令创建用户余额变更日志模型
+ *
+ * @param command 新增命令
+ * @return 用户余额变更日志模型
+ */
+ public UserBalanceLogModel create(AddUserBalanceLogCommand command) {
+ UserBalanceLogModel model = create();
+ model.loadAddCommand(command);
+ return model;
+ }
+
+ /**
+ * 根据更新命令创建用户余额变更日志模型
+ *
+ * @param command 更新命令
+ * @return 用户余额变更日志模型
+ */
+ public UserBalanceLogModel create(UpdateUserBalanceLogCommand command) {
+ UserBalanceLogModel model = create();
+ model.loadUpdateCommand(command);
+ return model;
+ }
+
+ /**
+ * 根据实体创建用户余额变更日志模型
+ *
+ * @param entity 实体
+ * @return 用户余额变更日志模型
+ */
+ public UserBalanceLogModel create(UserBalanceLogEntity entity) {
+ return new UserBalanceLogModel(entity, userBalanceLogService);
+ }
+
+}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/query/SearchUserBalanceLogQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/query/SearchUserBalanceLogQuery.java
new file mode 100644
index 0000000..2041dc6
--- /dev/null
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user_balance_log/query/SearchUserBalanceLogQuery.java
@@ -0,0 +1,59 @@
+package com.agileboot.domain.ab98.user_balance_log.query;
+
+import cn.hutool.core.util.StrUtil;
+import com.agileboot.common.core.page.AbstractPageQuery;
+import com.agileboot.domain.ab98.user_balance_log.db.UserBalanceLogEntity;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ *
+ * 用户余额变更日志查询参数
+ *
+ *
+ * @author valarchie
+ * @since 2025-12-06
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class SearchUserBalanceLogQuery extends AbstractPageQuery {
+
+ @ApiModelProperty("用户余额ID")
+ private Long userBalanceId;
+
+ @ApiModelProperty("变更类型(1-消费, 2-审批归还, 3-系统调整)")
+ private Integer changeType;
+
+ @ApiModelProperty("关联订单ID")
+ private Long orderId;
+
+ @ApiModelProperty("关联审批ID")
+ private Long approvalId;
+
+ @ApiModelProperty("关联订单商品ID")
+ private Long orderGoodsId;
+
+ @ApiModelProperty("创建者ID")
+ private Long creatorId;
+
+ @Override
+ public QueryWrapper addQueryCondition() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+
+ queryWrapper
+ .eq(userBalanceId != null, "user_balance_id", userBalanceId)
+ .eq(changeType != null, "change_type", changeType)
+ .eq(orderId != null, "order_id", orderId)
+ .eq(approvalId != null, "approval_id", approvalId)
+ .eq(orderGoodsId != null, "order_goods_id", orderGoodsId)
+ .eq(creatorId != null, "creator_id", creatorId)
+ .eq("deleted", false);
+
+ this.timeRangeColumn = "create_time";
+
+ return queryWrapper;
+ }
+
+}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/ReturnApprovalApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/ReturnApprovalApplicationService.java
index 3d52737..875ab48 100644
--- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/ReturnApprovalApplicationService.java
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/ReturnApprovalApplicationService.java
@@ -7,6 +7,9 @@ import com.agileboot.common.utils.MoneyUtil;
import com.agileboot.domain.ab98.user_balance.db.UserBalanceEntity;
import com.agileboot.domain.ab98.user_balance.db.UserBalanceService;
import com.agileboot.domain.ab98.user_balance.dto.UserBalanceDTO;
+import com.agileboot.domain.ab98.user_balance_log.command.AddUserBalanceLogCommand;
+import com.agileboot.domain.ab98.user_balance_log.model.UserBalanceLogModelFactory;
+
import org.springframework.beans.factory.annotation.Autowired;
import com.agileboot.common.constant.UrlConstants;
import com.agileboot.common.constant.WeixinConstants;
@@ -117,6 +120,7 @@ public class ReturnApprovalApplicationService {
private final CabinetCellOperationModelFactory cabinetCellOperationModelFactory;
private final MqttService mqttService;
private final UserBalanceService userBalanceService;
+ private final UserBalanceLogModelFactory userBalanceLogModelFactory;
@Autowired
private WxshopConfig wxshopConfig;
@@ -281,9 +285,33 @@ public class ReturnApprovalApplicationService {
if (null == userBalance) {
throw new IllegalArgumentException("用户余额不存在");
}
- userBalance.setBalance(userBalance.getBalance() + MoneyUtil.yuanToFen(command.getReturnAmount()));
- userBalance.setUseBalance(userBalance.getUseBalance() - MoneyUtil.yuanToFen(command.getReturnAmount()));
+
+ // 记录变更前的余额信息
+ Long useBalanceBefore = userBalance.getUseBalance();
+ Long changeAmountFen = MoneyUtil.yuanToFen(command.getReturnAmount());
+
+ // 更新余额
+ userBalance.setBalance(userBalance.getBalance() + changeAmountFen);
+ userBalance.setUseBalance(userBalance.getUseBalance() - changeAmountFen);
userBalance.updateById();
+
+ try {
+ // 记录余额变更日志
+ AddUserBalanceLogCommand logCommand = new AddUserBalanceLogCommand();
+ logCommand.setUserBalanceId(userBalance.getUserBalanceId());
+ logCommand.setChangeType(2); // 2-审批归还
+ logCommand.setChangeAmount(-changeAmountFen); // 审批归还为负数
+ logCommand.setUseBalanceBefore(useBalanceBefore);
+ logCommand.setUseBalanceAfter(userBalance.getUseBalance());
+ logCommand.setOrderId(orderModel.getOrderId());
+ logCommand.setApprovalId(model.getApprovalId());
+ logCommand.setOrderGoodsId(orderGoodsModel.getOrderGoodsId());
+ logCommand.initBaseEntity();
+
+ userBalanceLogModelFactory.create(logCommand).insert();
+ } catch (Exception e) {
+ log.error("记录余额变更日志失败", e);
+ }
/*if (null != qyUser) {
qyUser.setBalance(qyUser.getBalance().add(command.getReturnAmount()));
qyUser.setUseBalance(qyUser.getUseBalance().subtract(command.getReturnAmount()));
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java
index c527a3e..26278b4 100644
--- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java
@@ -7,6 +7,8 @@ import com.agileboot.common.utils.MoneyUtil;
import com.agileboot.domain.ab98.user_balance.db.UserBalanceEntity;
import com.agileboot.domain.ab98.user_balance.db.UserBalanceService;
import com.agileboot.domain.ab98.user_balance.dto.UserBalanceDTO;
+import com.agileboot.domain.ab98.user_balance_log.command.AddUserBalanceLogCommand;
+import com.agileboot.domain.ab98.user_balance_log.model.UserBalanceLogModelFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.common.exception.ApiException;
@@ -105,6 +107,7 @@ public class OrderApplicationService {
private final Ab98UserService ab98UserService;
private final WxUserService wxUserService;
private final UserBalanceService userBalanceService;
+ private final UserBalanceLogModelFactory userBalanceLogModelFactory;
@Autowired
private WxshopConfig wxshopConfig;
@@ -260,9 +263,28 @@ public class OrderApplicationService {
if (userBalance.getBalance().compareTo(MoneyUtil.yuanToFen(orderModel.getTotalAmount())) < 0) {
throw new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "余额不足");
} else {
- userBalance.setBalance(userBalance.getBalance() - MoneyUtil.yuanToFen(orderModel.getTotalAmount()));
- userBalance.setUseBalance(userBalance.getUseBalance() + MoneyUtil.yuanToFen(orderModel.getTotalAmount()));
+ // 记录变更前的余额信息
+ Long useBalanceBefore = userBalance.getUseBalance();
+ Long changeAmount = MoneyUtil.yuanToFen(orderModel.getTotalAmount());
+
+ userBalance.setBalance(userBalance.getBalance() - changeAmount);
+ userBalance.setUseBalance(userBalance.getUseBalance() + changeAmount);
userBalanceService.updateById(userBalance);
+
+ try {
+ // 记录余额变动日志
+ AddUserBalanceLogCommand logCommand = new AddUserBalanceLogCommand();
+ logCommand.setUserBalanceId(userBalance.getUserBalanceId());
+ logCommand.setChangeType(1); // 1-消费
+ logCommand.setChangeAmount(changeAmount); // 消费为正数
+ logCommand.setUseBalanceBefore(useBalanceBefore);
+ logCommand.setUseBalanceAfter(userBalance.getUseBalance());
+ logCommand.setOrderId(orderModel.getOrderId());
+ logCommand.setCreatorId(orderModel.getCreatorId());
+ userBalanceLogModelFactory.create(logCommand).save();
+ } catch (Exception e) {
+ log.error("记录余额变动日志失败", e);
+ }
}
// 金额转换(元转分)并四舍五入
diff --git a/sql/20251206_user_balance_log.sql b/sql/20251206_user_balance_log.sql
new file mode 100644
index 0000000..1d7aee5
--- /dev/null
+++ b/sql/20251206_user_balance_log.sql
@@ -0,0 +1,30 @@
+CREATE TABLE `user_balance_log` (
+ `log_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志ID',
+ `user_balance_id` bigint NOT NULL COMMENT '用户余额ID',
+
+ -- 余额变更信息
+ `change_type` tinyint NOT NULL COMMENT '变更类型(1-消费 2-审批归还 3-系统调整)',
+ `change_amount` bigint NOT NULL COMMENT '变更金额(单位:分,正数表示增加,负数表示减少)',
+ `use_balance_before` bigint NOT NULL COMMENT '变更前已用余额',
+ `use_balance_after` bigint NOT NULL COMMENT '变更后已用余额',
+
+ -- 关联表信息
+ `order_id` bigint DEFAULT NULL COMMENT '关联订单ID',
+ `approval_id` bigint DEFAULT NULL COMMENT '关联审批ID',
+ `order_goods_id` bigint DEFAULT NULL COMMENT '关联订单商品ID',
+
+ -- 系统字段
+ `creator_id` bigint NOT NULL DEFAULT 0 COMMENT '创建者ID',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater_id` bigint NOT NULL DEFAULT 0 COMMENT '更新者ID',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '删除标志(0存在 1删除)',
+
+ PRIMARY KEY (`log_id`),
+ KEY `idx_user_balance` (`user_balance_id`),
+ KEY `idx_order` (`order_id`),
+ KEY `idx_approval` (`approval_id`),
+ KEY `idx_change_type` (`change_type`),
+ KEY `idx_create_time` (`create_time`),
+ KEY `idx_order_goods` (`order_goods_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户余额变更日志表';