ソースを参照

知识库积分部分功能提交,点赞,阅读功能

徐滕 1 週間 前
コミット
ec66c72fdd
18 ファイル変更1606 行追加7 行削除
  1. 42 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeBaseLikeRecordDao.java
  2. 12 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeBasePointDetailDao.java
  3. 10 1
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeBasePointRuleDao.java
  4. 48 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeBaseReadRecordDao.java
  5. 93 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBaseLikeRecord.java
  6. 93 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBaseReadRecord.java
  7. 345 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgeBaseReadLikeService.java
  8. 213 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeBaseReadLikeController.java
  9. 21 0
      src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeBaseShareController.java
  10. 168 0
      src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBaseLikeRecordDao.xml
  11. 9 0
      src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBasePointDetailDao.xml
  12. 12 0
      src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBasePointRuleDao.xml
  13. 174 0
      src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBaseReadRecordDao.xml
  14. 59 0
      src/main/resources/mysqlDateBase/work_knowledge_base_read_like.sql
  15. 69 0
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseLikeDetail.jsp
  16. 64 0
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseReadDetail.jsp
  17. 41 1
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseShareDetail.jsp
  18. 133 5
      src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseShareList.jsp

+ 42 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeBaseLikeRecordDao.java

@@ -0,0 +1,42 @@
+package com.jeeplus.modules.WorkKnowledgeBase.dao;
+
+import com.jeeplus.common.persistence.CrudDao;
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.persistence.annotation.MyBatisDao;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseLikeRecord;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 知识库点赞记录DAO接口
+ */
+@MyBatisDao
+public interface WorkKnowledgeBaseLikeRecordDao extends CrudDao<WorkKnowledgeBaseLikeRecord> {
+
+    /**
+     * 根据文件ID和用户ID查询点赞记录
+     */
+    WorkKnowledgeBaseLikeRecord findByFileIdAndUserId(@Param("fileId") String fileId, @Param("userId") String userId);
+
+    /**
+     * 更新点赞状态(取消点赞)
+     */
+    void updateLikeStatus(WorkKnowledgeBaseLikeRecord record);
+
+    /**
+     * 统计文件的点赞人数(当前已点赞状态)
+     */
+    Integer countLikesByFileId(@Param("fileId") String fileId);
+
+    /**
+     * 查询文件的点赞详情列表(不分页)
+     */
+    List<WorkKnowledgeBaseLikeRecord> findLikeDetailList(@Param("fileId") String fileId);
+    
+    /**
+     * 查询文件的点赞详情列表(分页)
+     */
+    List<WorkKnowledgeBaseLikeRecord> findLikeDetailPage(Page<WorkKnowledgeBaseLikeRecord> page, @Param("fileId") String fileId);
+}

+ 12 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeBasePointDetailDao.java

@@ -27,4 +27,16 @@ public interface WorkKnowledgeBasePointDetailDao extends CrudDao<WorkKnowledgeBa
 
     /** 某用户积分明细总数 */
     int findUserPointDetailCount(UserPointDetail userPointDetail);
+    
+    /**
+     * 物理删除指定条件的积分记录(用于取消点赞时删除)
+     * @param userId 用户ID(积分接收人)
+     * @param shareId 文件ID
+     * @param changeType 变动类型(4=点赞积分)
+     */
+    void deleteByUserIdAndShareIdAndChangeType(
+        @org.apache.ibatis.annotations.Param("userId") String userId,
+        @org.apache.ibatis.annotations.Param("shareId") String shareId,
+        @org.apache.ibatis.annotations.Param("changeType") Integer changeType
+    );
 }

+ 10 - 1
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeBasePointRuleDao.java

@@ -28,7 +28,16 @@ public interface WorkKnowledgeBasePointRuleDao extends CrudDao<WorkKnowledgeBase
     int updateStatus(@Param("id") String id, @Param("status") Integer status, @Param("updateBy") String updateBy);
 
     /**
-     * 根据分类ID和规则类型查询积分规则
+     * 根据分类ID和规则类型查询积分规则(返回所有匹配记录)
      */
     WorkKnowledgeBasePointRule findByCategoryIdAndRuleType(@Param("categoryId") String categoryId, @Param("ruleType") String ruleType);
+    
+    /**
+     * 根据分类ID、规则类型和积分名称查询积分规则(精确匹配)
+     */
+    WorkKnowledgeBasePointRule findByCategoryIdAndRuleTypeAndPointName(
+        @Param("categoryId") String categoryId, 
+        @Param("ruleType") String ruleType,
+        @Param("pointName") String pointName
+    );
 }

+ 48 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/dao/WorkKnowledgeBaseReadRecordDao.java

@@ -0,0 +1,48 @@
+package com.jeeplus.modules.WorkKnowledgeBase.dao;
+
+import com.jeeplus.common.persistence.CrudDao;
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.persistence.annotation.MyBatisDao;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseReadRecord;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 知识库阅读记录DAO接口
+ */
+@MyBatisDao
+public interface WorkKnowledgeBaseReadRecordDao extends CrudDao<WorkKnowledgeBaseReadRecord> {
+
+    /**
+     * 根据文件ID和用户ID查询阅读记录
+     */
+    WorkKnowledgeBaseReadRecord findByFileIdAndUserId(@Param("fileId") String fileId, @Param("userId") String userId);
+
+    /**
+     * 更新阅读记录(增加访问次数,更新最近访问时间)
+     */
+    void updateReadRecord(WorkKnowledgeBaseReadRecord record);
+
+    /**
+     * 统计文件的阅读人数
+     */
+    Integer countReadersByFileId(@Param("fileId") String fileId);
+
+    /**
+     * 统计文件的总阅读量
+     */
+    Integer countReadsByFileId(@Param("fileId") String fileId);
+
+    /**
+     * 查询文件的阅读详情列表(不分页)
+     */
+    List<WorkKnowledgeBaseReadRecord> findReadDetailList(@Param("fileId") String fileId);
+    
+    /**
+     * 查询文件的阅读详情列表(分页)
+     */
+    List<WorkKnowledgeBaseReadRecord> findReadDetailPage(Page<WorkKnowledgeBaseReadRecord> page, @Param("fileId") String fileId);
+}

+ 93 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBaseLikeRecord.java

@@ -0,0 +1,93 @@
+package com.jeeplus.modules.WorkKnowledgeBase.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.jeeplus.common.persistence.DataEntity;
+import com.jeeplus.modules.sys.entity.User;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * 知识库点赞记录实体
+ * 对应表 work_knowledge_base_like_record
+ */
+public class WorkKnowledgeBaseLikeRecord extends DataEntity<WorkKnowledgeBaseLikeRecord> {
+
+    /** 文件ID(关联 work_knowledge_base_share_info.id) */
+    private String fileId;
+
+    /** 点赞人ID */
+    private String userId;
+
+    /** 点赞人对象(用于关联查询) */
+    private User user;
+
+    /** 点赞时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date likeTime;
+
+    /** 取消点赞时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date cancelTime;
+
+    /** 点赞状态(1:已点赞 0:已取消) */
+    private Integer status;
+
+    public WorkKnowledgeBaseLikeRecord() {
+        super();
+    }
+
+    public WorkKnowledgeBaseLikeRecord(String id) {
+        super(id);
+    }
+
+    public String getFileId() {
+        return fileId;
+    }
+
+    public void setFileId(String fileId) {
+        this.fileId = fileId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public User getUser() {
+        return user;
+    }
+
+    public void setUser(User user) {
+        this.user = user;
+    }
+
+    public Date getLikeTime() {
+        return likeTime;
+    }
+
+    public void setLikeTime(Date likeTime) {
+        this.likeTime = likeTime;
+    }
+
+    public Date getCancelTime() {
+        return cancelTime;
+    }
+
+    public void setCancelTime(Date cancelTime) {
+        this.cancelTime = cancelTime;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+}

+ 93 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/entity/WorkKnowledgeBaseReadRecord.java

@@ -0,0 +1,93 @@
+package com.jeeplus.modules.WorkKnowledgeBase.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.jeeplus.common.persistence.DataEntity;
+import com.jeeplus.modules.sys.entity.User;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * 知识库阅读记录实体
+ * 对应表 work_knowledge_base_read_record
+ */
+public class WorkKnowledgeBaseReadRecord extends DataEntity<WorkKnowledgeBaseReadRecord> {
+
+    /** 文件ID(关联 work_knowledge_base_share_info.id) */
+    private String fileId;
+
+    /** 访问人ID */
+    private String userId;
+
+    /** 访问人对象(用于关联查询) */
+    private User user;
+
+    /** 首次访问时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date firstReadTime;
+
+    /** 最近访问时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date lastReadTime;
+
+    /** 访问次数 */
+    private Integer readCount;
+
+    public WorkKnowledgeBaseReadRecord() {
+        super();
+    }
+
+    public WorkKnowledgeBaseReadRecord(String id) {
+        super(id);
+    }
+
+    public String getFileId() {
+        return fileId;
+    }
+
+    public void setFileId(String fileId) {
+        this.fileId = fileId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public User getUser() {
+        return user;
+    }
+
+    public void setUser(User user) {
+        this.user = user;
+    }
+
+    public Date getFirstReadTime() {
+        return firstReadTime;
+    }
+
+    public void setFirstReadTime(Date firstReadTime) {
+        this.firstReadTime = firstReadTime;
+    }
+
+    public Date getLastReadTime() {
+        return lastReadTime;
+    }
+
+    public void setLastReadTime(Date lastReadTime) {
+        this.lastReadTime = lastReadTime;
+    }
+
+    public Integer getReadCount() {
+        return readCount;
+    }
+
+    public void setReadCount(Integer readCount) {
+        this.readCount = readCount;
+    }
+}

+ 345 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/service/WorkKnowledgeBaseReadLikeService.java

@@ -0,0 +1,345 @@
+package com.jeeplus.modules.WorkKnowledgeBase.service;
+
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.service.CrudService;
+import com.jeeplus.common.utils.IdGen;
+import com.jeeplus.modules.WorkKnowledgeBase.dao.*;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseLikeRecord;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBasePointDetail;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBasePointRule;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseReadRecord;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseShareInfo;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgePointCategory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 知识库阅读和点赞Service
+ */
+@Service
+@Transactional(readOnly = true)
+public class WorkKnowledgeBaseReadLikeService extends CrudService<WorkKnowledgeBaseReadRecordDao, WorkKnowledgeBaseReadRecord> {
+
+    @Autowired
+    private WorkKnowledgeBaseReadRecordDao readRecordDao;
+
+    @Autowired
+    private WorkKnowledgeBaseLikeRecordDao likeRecordDao;
+
+    @Autowired
+    private WorkKnowledgeBasePointRuleDao pointRuleDao;
+
+    @Autowired
+    private WorkKnowledgeBaseShareInfoDao shareInfoDao;
+
+    @Autowired
+    private WorkKnowledgePointCategoryDao categoryDao;
+
+    @Autowired
+    private WorkKnowledgeBasePointDetailDao pointDetailDao;
+
+    /**
+     * 记录阅读行为(首次阅读给创建人加分)
+     * @param fileId 文件ID
+     * @param userId 访问人ID
+     * @return 是否首次阅读
+     */
+    @Transactional(readOnly = false)
+    public boolean recordRead(String fileId, String userId) {
+        if (fileId == null || userId == null) {
+            return false;
+        }
+
+        // 查询是否已有阅读记录
+        WorkKnowledgeBaseReadRecord existingRecord = readRecordDao.findByFileIdAndUserId(fileId, userId);
+
+        if (existingRecord != null) {
+            // 已存在记录,更新访问次数和最近访问时间
+            existingRecord.setLastReadTime(new Date());
+            existingRecord.setReadCount(existingRecord.getReadCount() + 1);
+            existingRecord.preUpdate();
+            readRecordDao.updateReadRecord(existingRecord);
+            return false; // 非首次阅读
+        } else {
+            // 首次阅读,创建新记录
+            WorkKnowledgeBaseReadRecord newRecord = new WorkKnowledgeBaseReadRecord();
+            newRecord.setFileId(fileId);
+            newRecord.setUserId(userId);
+            Date now = new Date();
+            newRecord.setFirstReadTime(now);
+            newRecord.setLastReadTime(now);
+            newRecord.setReadCount(1);
+            newRecord.preInsert();
+            readRecordDao.insert(newRecord);
+
+            // 首次阅读:给创建人发放积分
+            grantPointsForFirstRead(fileId, userId);
+            return true; // 首次阅读
+        }
+    }
+
+    /**
+     * 点赞操作
+     * @param fileId 文件ID
+     * @param userId 点赞人ID
+     * @return 是否首次点赞(用于前端提示)
+     */
+    @Transactional(readOnly = false)
+    public boolean likeFile(String fileId, String userId) {
+        if (fileId == null || userId == null) {
+            return false;
+        }
+
+        // 查询是否有点赞记录
+        WorkKnowledgeBaseLikeRecord existingRecord = likeRecordDao.findByFileIdAndUserId(fileId, userId);
+
+        if (existingRecord != null) {
+            if (existingRecord.getStatus() == 1) {
+                // 已经点赞过了,不需要重复操作
+                return false;
+            } else {
+                // 之前取消过点赞,现在重新点赞
+                existingRecord.setStatus(1);
+                existingRecord.setLikeTime(new Date());
+                existingRecord.setCancelTime(null);
+                existingRecord.preUpdate();
+                likeRecordDao.updateLikeStatus(existingRecord);
+
+                // 重新点赞也给创建人发放积分(仅一次)
+                grantPointsForFirstLike(fileId, userId);
+                return true;
+            }
+        } else {
+            // 首次点赞,创建新记录
+            WorkKnowledgeBaseLikeRecord newRecord = new WorkKnowledgeBaseLikeRecord();
+            newRecord.setFileId(fileId);
+            newRecord.setUserId(userId);
+            newRecord.setLikeTime(new Date());
+            newRecord.setStatus(1);
+            newRecord.preInsert();
+            likeRecordDao.insert(newRecord);
+
+            // 首次点赞:给创建人发放积分
+            grantPointsForFirstLike(fileId, userId);
+            return true; // 首次点赞
+        }
+    }
+
+    /**
+     * 取消点赞操作(同时删除对应的积分记录)
+     * @param fileId 文件ID
+     * @param userId 点赞人ID
+     */
+    @Transactional(readOnly = false)
+    public void cancelLike(String fileId, String userId) {
+        if (fileId == null || userId == null) {
+            return;
+        }
+
+        WorkKnowledgeBaseLikeRecord record = likeRecordDao.findByFileIdAndUserId(fileId, userId);
+        if (record != null && record.getStatus() == 1) {
+            // 更新点赞状态为已取消
+            record.setStatus(0);
+            record.setCancelTime(new Date());
+            record.preUpdate();
+            likeRecordDao.updateLikeStatus(record);
+            
+            // 删除对应的点赞积分记录(避免重复刷分)
+            deletePointDetailForCancelLike(fileId, userId);
+        }
+    }
+
+    /**
+     * 统计文件的阅读量(去重后的用户数量,每人每文件只计1次)
+     */
+    public Integer getReadCount(String fileId) {
+        if (fileId == null) {
+            return 0;
+        }
+        Integer count = readRecordDao.countReadersByFileId(fileId);
+        return count != null ? count : 0;
+    }
+
+    /**
+     * 统计文件的点赞量
+     */
+    public Integer getLikeCount(String fileId) {
+        if (fileId == null) {
+            return 0;
+        }
+        Integer count = likeRecordDao.countLikesByFileId(fileId);
+        return count != null ? count : 0;
+    }
+
+    /**
+     * 查询文件的阅读详情列表(不分页)
+     */
+    public List<WorkKnowledgeBaseReadRecord> getReadDetailList(String fileId) {
+        return readRecordDao.findReadDetailList(fileId);
+    }
+    
+    /**
+     * 查询文件的阅读详情列表(分页)
+     */
+    public Page<WorkKnowledgeBaseReadRecord> findReadDetailPage(Page<WorkKnowledgeBaseReadRecord> page, String fileId) {
+        page.setList(readRecordDao.findReadDetailPage(page, fileId));
+        return page;
+    }
+
+    /**
+     * 查询文件的点赞详情列表(不分页)
+     */
+    public List<WorkKnowledgeBaseLikeRecord> getLikeDetailList(String fileId) {
+        return likeRecordDao.findLikeDetailList(fileId);
+    }
+    
+    /**
+     * 查询文件的点赞详情列表(分页)
+     */
+    public Page<WorkKnowledgeBaseLikeRecord> findLikeDetailPage(Page<WorkKnowledgeBaseLikeRecord> page, String fileId) {
+        page.setList(likeRecordDao.findLikeDetailPage(page, fileId));
+        return page;
+    }
+
+    /**
+     * 检查用户是否已点赞
+     */
+    public boolean isLiked(String fileId, String userId) {
+        if (fileId == null || userId == null) {
+            return false;
+        }
+        WorkKnowledgeBaseLikeRecord record = likeRecordDao.findByFileIdAndUserId(fileId, userId);
+        return record != null && record.getStatus() == 1;
+    }
+
+    /**
+     * 首次阅读时给创建人发放积分
+     */
+    private void grantPointsForFirstRead(String fileId, String readerUserId) {
+        try {
+            // 查询文件信息获取创建人
+            WorkKnowledgeBaseShareInfo shareInfo = shareInfoDao.get(fileId);
+            if (shareInfo == null || shareInfo.getCreateBy() == null) {
+                return;
+            }
+
+            String creatorUserId = shareInfo.getCreateBy().getId();
+            if (creatorUserId.equals(readerUserId)) {
+                // 自己阅读自己的文件,不给积分
+                return;
+            }
+
+            // 查询"基础积分"分类的积分规则
+            List<WorkKnowledgePointCategory> categories = categoryDao.findList(
+                new WorkKnowledgePointCategory() {{ setCategoryName("基础积分"); }}
+            );
+
+            if (categories == null || categories.isEmpty()) {
+                return;
+            }
+
+            String categoryId = categories.get(0).getId();
+            
+            // 使用新方法精确查询:category_id + rule_type=10 + point_name="阅读"
+            WorkKnowledgeBasePointRule rule = pointRuleDao.findByCategoryIdAndRuleTypeAndPointName(categoryId, "10", "阅读");
+            
+            if (rule != null && rule.getPointValue() != null) {
+                int pointValue = Integer.parseInt(rule.getPointValue());
+                if (pointValue > 0) {
+                    // 插入积分明细(change_type=3 表示阅读积分)
+                    insertPointDetail(creatorUserId, fileId, 3, pointValue);
+                }
+            }
+        } catch (Exception e) {
+            // 积分发放失败不影响阅读记录,记录日志即可
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 首次点赞时给创建人发放积分
+     */
+    private void grantPointsForFirstLike(String fileId, String likerUserId) {
+        try {
+            // 查询文件信息获取创建人
+            WorkKnowledgeBaseShareInfo shareInfo = shareInfoDao.get(fileId);
+            if (shareInfo == null || shareInfo.getCreateBy() == null) {
+                return;
+            }
+
+            String creatorUserId = shareInfo.getCreateBy().getId();
+            if (creatorUserId.equals(likerUserId)) {
+                // 自己点赞自己的文件,不给积分
+                return;
+            }
+
+            // 查询"基础积分"分类的积分规则
+            List<WorkKnowledgePointCategory> categories = categoryDao.findList(
+                new WorkKnowledgePointCategory() {{ setCategoryName("基础积分"); }}
+            );
+
+            if (categories == null || categories.isEmpty()) {
+                return;
+            }
+
+            String categoryId = categories.get(0).getId();
+            
+            // 使用新方法精确查询:category_id + rule_type=10 + point_name="点赞"
+            WorkKnowledgeBasePointRule rule = pointRuleDao.findByCategoryIdAndRuleTypeAndPointName(
+                categoryId, "10", "点赞"
+            );
+            
+            if (rule != null && rule.getPointValue() != null) {
+                int pointValue = Integer.parseInt(rule.getPointValue());
+                if (pointValue > 0) {
+                    // 插入积分明细(change_type=4 表示点赞积分)
+                    insertPointDetail(creatorUserId, fileId, 4, pointValue);
+                }
+            }
+        } catch (Exception e) {
+            // 积分发放失败不影响点赞记录,记录日志即可
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 插入积分明细记录
+     */
+    private void insertPointDetail(String userId, String shareId, Integer changeType, Integer point) {
+        WorkKnowledgeBasePointDetail detail = new WorkKnowledgeBasePointDetail();
+        detail.setUserId(userId);
+        detail.setShareId(shareId);
+        detail.setChangeType(changeType);
+        detail.setPoint(point);
+        detail.preInsert();
+        pointDetailDao.insert(detail);
+    }
+    
+    /**
+     * 取消点赞时删除对应的点赞积分记录(物理删除)
+     * @param fileId 文件ID
+     * @param likerUserId 点赞人ID
+     */
+    private void deletePointDetailForCancelLike(String fileId, String likerUserId) {
+        try {
+            // 查询文件信息获取创建人
+            WorkKnowledgeBaseShareInfo shareInfo = shareInfoDao.get(fileId);
+            if (shareInfo == null || shareInfo.getCreateBy() == null) {
+                return;
+            }
+            
+            String creatorUserId = shareInfo.getCreateBy().getId();
+            
+            // 物理删除创建人的点赞积分记录(change_type=4表示点赞积分)
+            pointDetailDao.deleteByUserIdAndShareIdAndChangeType(creatorUserId, fileId, 4);
+        } catch (Exception e) {
+            // 删除失败不影响取消点赞操作,记录日志即可
+            e.printStackTrace();
+        }
+    }
+}

+ 213 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeBaseReadLikeController.java

@@ -0,0 +1,213 @@
+package com.jeeplus.modules.WorkKnowledgeBase.web;
+
+import com.jeeplus.common.persistence.Page;
+import com.jeeplus.common.web.BaseController;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseLikeRecord;
+import com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBaseReadRecord;
+import com.jeeplus.modules.WorkKnowledgeBase.service.WorkKnowledgeBaseReadLikeService;
+import com.jeeplus.modules.sys.utils.UserUtils;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 知识库阅读和点赞Controller
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/workKnowledgeBase/readLike")
+public class WorkKnowledgeBaseReadLikeController extends BaseController {
+
+    @Autowired
+    private WorkKnowledgeBaseReadLikeService readLikeService;
+
+    /**
+     * 记录阅读行为(用户查看详情时调用)
+     */
+    @RequestMapping(value = "recordRead")
+    @ResponseBody
+    public Map<String, Object> recordRead(@RequestParam String fileId) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            String userId = UserUtils.getUser().getId();
+            boolean isFirstRead = readLikeService.recordRead(fileId, userId);
+            
+            result.put("success", true);
+            result.put("isFirstRead", isFirstRead);
+            result.put("readCount", readLikeService.getReadCount(fileId));
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "记录阅读失败:" + e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 点赞操作
+     */
+    @RequestMapping(value = "like")
+    @ResponseBody
+    public Map<String, Object> like(@RequestParam String fileId) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            String userId = UserUtils.getUser().getId();
+            boolean isFirstLike = readLikeService.likeFile(fileId, userId);
+            
+            result.put("success", true);
+            result.put("isFirstLike", isFirstLike);
+            result.put("likeCount", readLikeService.getLikeCount(fileId));
+            result.put("liked", true);
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "点赞失败:" + e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 取消点赞操作
+     */
+    @RequestMapping(value = "cancelLike")
+    @ResponseBody
+    public Map<String, Object> cancelLike(@RequestParam String fileId) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            String userId = UserUtils.getUser().getId();
+            readLikeService.cancelLike(fileId, userId);
+            
+            result.put("success", true);
+            result.put("likeCount", readLikeService.getLikeCount(fileId));
+            result.put("liked", false);
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "取消点赞失败:" + e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 获取文件的阅读量和点赞量统计
+     */
+    @RequestMapping(value = "getCounts")
+    @ResponseBody
+    public Map<String, Object> getCounts(@RequestParam String fileId) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            result.put("success", true);
+            result.put("readCount", readLikeService.getReadCount(fileId));
+            result.put("likeCount", readLikeService.getLikeCount(fileId));
+            
+            String userId = UserUtils.getUser().getId();
+            result.put("liked", readLikeService.isLiked(fileId, userId));
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "获取统计信息失败:" + e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 查询阅读详情列表(已废弃,改用readDetailPage页面方式)
+     */
+    @Deprecated
+    @RequestMapping(value = "readDetailList")
+    @ResponseBody
+    public Map<String, Object> readDetailList(@RequestParam String fileId) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            List<WorkKnowledgeBaseReadRecord> detailList = readLikeService.getReadDetailList(fileId);
+            result.put("success", true);
+            result.put("data", detailList);
+            result.put("totalCount", readLikeService.getReadCount(fileId));
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "查询阅读详情失败:" + e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 查询点赞详情列表(已废弃,改用likeDetailPage页面方式)
+     */
+    @Deprecated
+    @RequestMapping(value = "likeDetailList")
+    @ResponseBody
+    public Map<String, Object> likeDetailList(@RequestParam String fileId) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            List<WorkKnowledgeBaseLikeRecord> detailList = readLikeService.getLikeDetailList(fileId);
+            result.put("success", true);
+            result.put("data", detailList);
+            result.put("totalCount", readLikeService.getLikeCount(fileId));
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "查询点赞详情失败:" + e.getMessage());
+        }
+        return result;
+    }
+    
+    /**
+     * 阅读详情页面
+     */
+    @RequestMapping(value = "readDetailPage")
+    public String readDetailPage(@RequestParam String fileId, 
+                                  @RequestParam(required=false) Integer pageNo,
+                                  @RequestParam(required=false) Integer pageSize,
+                                  Model model) {
+        model.addAttribute("fileId", fileId);
+        
+        // 创建分页对象
+        Page<WorkKnowledgeBaseReadRecord> page = new Page<>();
+        if (pageNo != null && pageNo > 0) {
+            page.setPageNo(pageNo);
+        }
+        if (pageSize != null && pageSize > 0) {
+            page.setPageSize(pageSize);
+        } else {
+            page.setPageSize(10); // 默认每页10条
+        }
+        
+        // 查询阅读详情列表(分页)
+        Page<WorkKnowledgeBaseReadRecord> detailPage = readLikeService.findReadDetailPage(page, fileId);
+        model.addAttribute("page", detailPage);
+        model.addAttribute("totalCount", readLikeService.getReadCount(fileId));
+        
+        return "modules/WorkKnowledgeBase/workKnowledgeBaseReadDetail";
+    }
+    
+    /**
+     * 点赞详情页面
+     */
+    @RequestMapping(value = "likeDetailPage")
+    public String likeDetailPage(@RequestParam String fileId,
+                                  @RequestParam(required=false) Integer pageNo,
+                                  @RequestParam(required=false) Integer pageSize,
+                                  Model model) {
+        model.addAttribute("fileId", fileId);
+        
+        // 创建分页对象
+        Page<WorkKnowledgeBaseLikeRecord> page = new Page<>();
+        if (pageNo != null && pageNo > 0) {
+            page.setPageNo(pageNo);
+        }
+        if (pageSize != null && pageSize > 0) {
+            page.setPageSize(pageSize);
+        } else {
+            page.setPageSize(10); // 默认每页10条
+        }
+        
+        // 查询点赞详情列表(分页)
+        Page<WorkKnowledgeBaseLikeRecord> detailPage = readLikeService.findLikeDetailPage(page, fileId);
+        model.addAttribute("page", detailPage);
+        model.addAttribute("totalCount", readLikeService.getLikeCount(fileId));
+        
+        return "modules/WorkKnowledgeBase/workKnowledgeBaseLikeDetail";
+    }
+}

+ 21 - 0
src/main/java/com/jeeplus/modules/WorkKnowledgeBase/web/WorkKnowledgeBaseShareController.java

@@ -43,6 +43,9 @@ public class WorkKnowledgeBaseShareController extends BaseController {
     @Autowired
     private com.jeeplus.modules.WorkKnowledgeBase.service.WorkKnowledgeBasePointRuleService pointRuleService;
 
+    @Autowired
+    private com.jeeplus.modules.WorkKnowledgeBase.service.WorkKnowledgeBaseReadLikeService readLikeService;
+
     @ModelAttribute
     public WorkKnowledgeBaseShareInfo get(@RequestParam(required = false) String id) {
         WorkKnowledgeBaseShareInfo entity = null;
@@ -122,6 +125,18 @@ public class WorkKnowledgeBaseShareController extends BaseController {
                     map.put("treeName", treeInfo.getTreeName());
                 }
             }
+            
+            // 获取阅读量和点赞量
+            Object fileIdObj = map.get("id");
+            if (fileIdObj != null) {
+                String fileId = fileIdObj.toString();
+                map.put("readCount", readLikeService.getReadCount(fileId));
+                map.put("likeCount", readLikeService.getLikeCount(fileId));
+                
+                // 检查当前用户是否已点赞
+                boolean liked = readLikeService.isLiked(fileId, currentUserId);
+                map.put("liked", liked);
+            }
         }
 
 
@@ -177,6 +192,12 @@ public class WorkKnowledgeBaseShareController extends BaseController {
         model.addAttribute("rootId", rootId);
         model.addAttribute("dynValues", dynValues);
 
+        // 获取阅读量和点赞量
+        if (StringUtils.isNotBlank(shareInfo.getId())) {
+            model.addAttribute("readCount", readLikeService.getReadCount(shareInfo.getId()));
+            model.addAttribute("likeCount", readLikeService.getLikeCount(shareInfo.getId()));
+        }
+
         // 加载审核记录(审核中、审核通过、审核未通过状态时展示)
         String auditStatus = shareInfo.getAuditStatus();
         if (auditStatus != null && (auditStatus.equals("1") || auditStatus.equals("2") || auditStatus.equals("4") || auditStatus.equals("5"))) {

+ 168 - 0
src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBaseLikeRecordDao.xml

@@ -0,0 +1,168 @@
+<?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="com.jeeplus.modules.WorkKnowledgeBase.dao.WorkKnowledgeBaseLikeRecordDao">
+
+    <sql id="likeRecordColumns">
+        a.id AS "id",
+        a.file_id AS "fileId",
+        a.user_id AS "userId",
+        a.like_time AS "likeTime",
+        a.cancel_time AS "cancelTime",
+        a.status AS "status",
+        a.create_by AS "createBy.id",
+        a.create_date AS "createDate",
+        a.update_by AS "updateBy.id",
+        a.update_date AS "updateDate",
+        a.remarks AS "remarks",
+        a.del_flag AS "delFlag"
+    </sql>
+
+    <sql id="likeRecordJoins">
+        LEFT JOIN sys_user createBy ON createBy.id = a.create_by
+        LEFT JOIN sys_user updateBy ON updateBy.id = a.update_by
+    </sql>
+
+    <select id="get" resultType="WorkKnowledgeBaseLikeRecord">
+        SELECT
+            <include refid="likeRecordColumns"/>
+        FROM work_knowledge_base_like_record a
+        <include refid="likeRecordJoins"/>
+        WHERE a.id = #{id}
+    </select>
+
+    <select id="findList" resultType="WorkKnowledgeBaseLikeRecord">
+        SELECT
+            <include refid="likeRecordColumns"/>
+        FROM work_knowledge_base_like_record a
+        <include refid="likeRecordJoins"/>
+        <where>
+            a.del_flag = 0
+            <if test="fileId != null and fileId != ''">
+                AND a.file_id = #{fileId}
+            </if>
+            <if test="userId != null and userId != ''">
+                AND a.user_id = #{userId}
+            </if>
+            <if test="status != null">
+                AND a.status = #{status}
+            </if>
+        </where>
+        <choose>
+            <when test="page !=null and page.orderBy != null and page.orderBy != ''">
+                ORDER BY ${page.orderBy}
+            </when>
+            <otherwise>
+                ORDER BY a.create_date DESC
+            </otherwise>
+        </choose>
+    </select>
+
+    <insert id="insert">
+        INSERT INTO work_knowledge_base_like_record(
+            id,
+            file_id,
+            user_id,
+            like_time,
+            cancel_time,
+            status,
+            create_by,
+            create_date,
+            update_by,
+            update_date,
+            remarks,
+            del_flag
+        ) VALUES (
+            #{id},
+            #{fileId},
+            #{userId},
+            #{likeTime},
+            #{cancelTime},
+            #{status},
+            #{createBy.id},
+            #{createDate},
+            #{updateBy.id},
+            #{updateDate},
+            #{remarks},
+            #{delFlag}
+        )
+    </insert>
+
+    <update id="update">
+        UPDATE work_knowledge_base_like_record SET
+            status = #{status},
+            cancel_time = #{cancelTime},
+            update_by = #{updateBy.id},
+            update_date = #{updateDate},
+            remarks = #{remarks}
+        WHERE id = #{id}
+    </update>
+
+    <update id="delete">
+        UPDATE work_knowledge_base_like_record SET
+            del_flag = #{DEL_FLAG_DELETE}
+        WHERE id = #{id}
+    </update>
+
+    <!-- 根据文件ID和用户ID查询点赞记录 -->
+    <select id="findByFileIdAndUserId" resultType="WorkKnowledgeBaseLikeRecord">
+        SELECT
+            <include refid="likeRecordColumns"/>
+        FROM work_knowledge_base_like_record a
+        WHERE a.file_id = #{fileId}
+          AND a.user_id = #{userId}
+          AND a.del_flag = 0
+        LIMIT 1
+    </select>
+
+    <!-- 更新点赞状态(取消点赞) -->
+    <update id="updateLikeStatus">
+        UPDATE work_knowledge_base_like_record SET
+            status = #{status},
+            cancel_time = #{cancelTime},
+            update_by = #{updateBy.id},
+            update_date = #{updateDate}
+        WHERE id = #{id}
+    </update>
+
+    <!-- 统计文件的点赞人数(当前已点赞状态) -->
+    <select id="countLikesByFileId" resultType="java.lang.Integer">
+        SELECT COUNT(DISTINCT a.user_id)
+        FROM work_knowledge_base_like_record a
+        WHERE a.file_id = #{fileId}
+          AND a.status = 1
+          AND a.del_flag = 0
+    </select>
+
+    <!-- 查询文件的点赞详情列表(不分页) -->
+    <select id="findLikeDetailList" resultType="WorkKnowledgeBaseLikeRecord">
+        SELECT
+            a.id AS "id",
+            a.file_id AS "fileId",
+            a.user_id AS "userId",
+            u.name AS "user.name",
+            a.like_time AS "likeTime",
+            a.status AS "status"
+        FROM work_knowledge_base_like_record a
+        LEFT JOIN sys_user u ON u.id = a.user_id
+        WHERE a.file_id = #{fileId}
+          AND a.del_flag = 0
+        ORDER BY a.like_time DESC
+    </select>
+    
+    <!-- 查询文件的点赞详情列表(分页) -->
+    <select id="findLikeDetailPage" resultType="WorkKnowledgeBaseLikeRecord">
+        SELECT
+            a.id AS "id",
+            a.file_id AS "fileId",
+            a.user_id AS "userId",
+            u.name AS "user.name",
+            a.like_time AS "likeTime",
+            a.status AS "status"
+        FROM work_knowledge_base_like_record a
+        LEFT JOIN sys_user u ON u.id = a.user_id
+        WHERE a.file_id = #{fileId}
+          AND a.del_flag = 0
+        ORDER BY a.like_time DESC
+    </select>
+
+</mapper>

+ 9 - 0
src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBasePointDetailDao.xml

@@ -228,5 +228,14 @@
             </if>
         </where>
     </select>
+    
+    <!-- 物理删除指定条件的积分记录(用于取消点赞时删除) -->
+    <delete id="deleteByUserIdAndShareIdAndChangeType">
+        DELETE FROM work_knowledge_base_point_detail
+        WHERE user_id = #{userId}
+          AND share_id = #{shareId}
+          AND change_type = #{changeType}
+          AND del_flag = '0'
+    </delete>
 
 </mapper>

+ 12 - 0
src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBasePointRuleDao.xml

@@ -171,5 +171,17 @@
           AND a.del_flag = '0'
         LIMIT 1
     </select>
+    
+    <!-- 根据分类ID、规则类型和积分名称查询积分规则(精确匹配) -->
+    <select id="findByCategoryIdAndRuleTypeAndPointName" resultType="com.jeeplus.modules.WorkKnowledgeBase.entity.WorkKnowledgeBasePointRule">
+        SELECT <include refid="pointRuleColumns"/>
+        FROM work_knowledge_base_point_rule a
+        WHERE a.category_id = #{categoryId}
+          AND a.rule_type = #{ruleType}
+          AND a.point_name = #{pointName}
+          AND a.status = 1
+          AND a.del_flag = '0'
+        LIMIT 1
+    </select>
 
 </mapper>

+ 174 - 0
src/main/resources/mappings/modules/WorkKnowledgeBase/WorkKnowledgeBaseReadRecordDao.xml

@@ -0,0 +1,174 @@
+<?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="com.jeeplus.modules.WorkKnowledgeBase.dao.WorkKnowledgeBaseReadRecordDao">
+
+    <sql id="readRecordColumns">
+        a.id AS "id",
+        a.file_id AS "fileId",
+        a.user_id AS "userId",
+        a.first_read_time AS "firstReadTime",
+        a.last_read_time AS "lastReadTime",
+        a.read_count AS "readCount",
+        a.create_by AS "createBy.id",
+        a.create_date AS "createDate",
+        a.update_by AS "updateBy.id",
+        a.update_date AS "updateDate",
+        a.remarks AS "remarks",
+        a.del_flag AS "delFlag"
+    </sql>
+
+    <sql id="readRecordJoins">
+        LEFT JOIN sys_user createBy ON createBy.id = a.create_by
+        LEFT JOIN sys_user updateBy ON updateBy.id = a.update_by
+    </sql>
+
+    <select id="get" resultType="WorkKnowledgeBaseReadRecord">
+        SELECT
+            <include refid="readRecordColumns"/>
+        FROM work_knowledge_base_read_record a
+        <include refid="readRecordJoins"/>
+        WHERE a.id = #{id}
+    </select>
+
+    <select id="findList" resultType="WorkKnowledgeBaseReadRecord">
+        SELECT
+            <include refid="readRecordColumns"/>
+        FROM work_knowledge_base_read_record a
+        <include refid="readRecordJoins"/>
+        <where>
+            a.del_flag = 0
+            <if test="fileId != null and fileId != ''">
+                AND a.file_id = #{fileId}
+            </if>
+            <if test="userId != null and userId != ''">
+                AND a.user_id = #{userId}
+            </if>
+        </where>
+        <choose>
+            <when test="page !=null and page.orderBy != null and page.orderBy != ''">
+                ORDER BY ${page.orderBy}
+            </when>
+            <otherwise>
+                ORDER BY a.create_date DESC
+            </otherwise>
+        </choose>
+    </select>
+
+    <insert id="insert">
+        INSERT INTO work_knowledge_base_read_record(
+            id,
+            file_id,
+            user_id,
+            first_read_time,
+            last_read_time,
+            read_count,
+            create_by,
+            create_date,
+            update_by,
+            update_date,
+            remarks,
+            del_flag
+        ) VALUES (
+            #{id},
+            #{fileId},
+            #{userId},
+            #{firstReadTime},
+            #{lastReadTime},
+            #{readCount},
+            #{createBy.id},
+            #{createDate},
+            #{updateBy.id},
+            #{updateDate},
+            #{remarks},
+            #{delFlag}
+        )
+    </insert>
+
+    <update id="update">
+        UPDATE work_knowledge_base_read_record SET
+            last_read_time = #{lastReadTime},
+            read_count = #{readCount},
+            update_by = #{updateBy.id},
+            update_date = #{updateDate},
+            remarks = #{remarks}
+        WHERE id = #{id}
+    </update>
+
+    <update id="delete">
+        UPDATE work_knowledge_base_read_record SET
+            del_flag = #{DEL_FLAG_DELETE}
+        WHERE id = #{id}
+    </update>
+
+    <!-- 根据文件ID和用户ID查询阅读记录 -->
+    <select id="findByFileIdAndUserId" resultType="WorkKnowledgeBaseReadRecord">
+        SELECT
+            <include refid="readRecordColumns"/>
+        FROM work_knowledge_base_read_record a
+        WHERE a.file_id = #{fileId}
+          AND a.user_id = #{userId}
+          AND a.del_flag = 0
+        LIMIT 1
+    </select>
+
+    <!-- 更新阅读记录(增加访问次数,更新最近访问时间) -->
+    <update id="updateReadRecord">
+        UPDATE work_knowledge_base_read_record SET
+            last_read_time = #{lastReadTime},
+            read_count = #{readCount},
+            update_by = #{updateBy.id},
+            update_date = #{updateDate}
+        WHERE id = #{id}
+    </update>
+
+    <!-- 统计文件的阅读人数 -->
+    <select id="countReadersByFileId" resultType="java.lang.Integer">
+        SELECT COUNT(DISTINCT a.user_id)
+        FROM work_knowledge_base_read_record a
+        WHERE a.file_id = #{fileId}
+          AND a.del_flag = 0
+    </select>
+
+    <!-- 统计文件的总阅读量 -->
+    <select id="countReadsByFileId" resultType="java.lang.Integer">
+        SELECT COALESCE(SUM(a.read_count), 0)
+        FROM work_knowledge_base_read_record a
+        WHERE a.file_id = #{fileId}
+          AND a.del_flag = 0
+    </select>
+
+    <!-- 查询文件的阅读详情列表(不分页) -->
+    <select id="findReadDetailList" resultType="WorkKnowledgeBaseReadRecord">
+        SELECT
+            a.id AS "id",
+            a.file_id AS "fileId",
+            a.user_id AS "userId",
+            u.name AS "user.name",
+            a.first_read_time AS "firstReadTime",
+            a.last_read_time AS "lastReadTime",
+            a.read_count AS "readCount"
+        FROM work_knowledge_base_read_record a
+        LEFT JOIN sys_user u ON u.id = a.user_id
+        WHERE a.file_id = #{fileId}
+          AND a.del_flag = 0
+        ORDER BY a.first_read_time DESC
+    </select>
+    
+    <!-- 查询文件的阅读详情列表(分页) -->
+    <select id="findReadDetailPage" resultType="WorkKnowledgeBaseReadRecord">
+        SELECT
+            a.id AS "id",
+            a.file_id AS "fileId",
+            a.user_id AS "userId",
+            u.name AS "user.name",
+            a.first_read_time AS "firstReadTime",
+            a.last_read_time AS "lastReadTime",
+            a.read_count AS "readCount"
+        FROM work_knowledge_base_read_record a
+        LEFT JOIN sys_user u ON u.id = a.user_id
+        WHERE a.file_id = #{fileId}
+          AND a.del_flag = 0
+        ORDER BY a.first_read_time DESC
+    </select>
+
+</mapper>

+ 59 - 0
src/main/resources/mysqlDateBase/work_knowledge_base_read_like.sql

@@ -0,0 +1,59 @@
+-- =====================================================
+-- 知识库阅读记录表和点赞记录表
+-- 功能说明:
+-- 1. 记录用户对知识库文件的阅读和点赞行为
+-- 2. 首次阅读/点赞时给创建人发放积分
+-- 3. 统计并展示阅读量和点赞量
+-- =====================================================
+
+-- 1. 阅读记录表
+CREATE TABLE IF NOT EXISTS `work_knowledge_base_read_record` (
+  `id` VARCHAR(64) NOT NULL COMMENT '主键ID',
+  `file_id` VARCHAR(64) NOT NULL COMMENT '文件ID(关联 work_knowledge_base_share_info.id)',
+  `user_id` VARCHAR(64) NOT NULL COMMENT '访问人ID',
+  `first_read_time` DATETIME NOT NULL COMMENT '首次访问时间',
+  `last_read_time` DATETIME NOT NULL COMMENT '最近访问时间',
+  `read_count` INT(11) NOT NULL DEFAULT 1 COMMENT '访问次数',
+  `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
+  `create_date` DATETIME DEFAULT NULL COMMENT '创建时间',
+  `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
+  `update_date` DATETIME DEFAULT NULL COMMENT '更新时间',
+  `remarks` VARCHAR(255) DEFAULT NULL COMMENT '备注信息',
+  `del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标记(0:正常 1:删除)',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_file_user` (`file_id`, `user_id`, `del_flag`) COMMENT '同一用户对同一文件只有一条记录',
+  KEY `idx_file_id` (`file_id`),
+  KEY `idx_user_id` (`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库阅读记录表';
+
+-- 2. 点赞记录表
+CREATE TABLE IF NOT EXISTS `work_knowledge_base_like_record` (
+  `id` VARCHAR(64) NOT NULL COMMENT '主键ID',
+  `file_id` VARCHAR(64) NOT NULL COMMENT '文件ID(关联 work_knowledge_base_share_info.id)',
+  `user_id` VARCHAR(64) NOT NULL COMMENT '点赞人ID',
+  `like_time` DATETIME NOT NULL COMMENT '点赞时间',
+  `cancel_time` DATETIME DEFAULT NULL COMMENT '取消点赞时间',
+  `status` TINYINT(4) NOT NULL DEFAULT 1 COMMENT '点赞状态(1:已点赞 0:已取消)',
+  `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
+  `create_date` DATETIME DEFAULT NULL COMMENT '创建时间',
+  `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
+  `update_date` DATETIME DEFAULT NULL COMMENT '更新时间',
+  `remarks` VARCHAR(255) DEFAULT NULL COMMENT '备注信息',
+  `del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标记(0:正常 1:删除)',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_file_user` (`file_id`, `user_id`, `del_flag`) COMMENT '同一用户对同一文件只有一条记录',
+  KEY `idx_file_id` (`file_id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库点赞记录表';
+
+-- 3. 为 work_knowledge_base_share_info 表添加阅读量和点赞量字段(可选,用于缓存统计)
+-- ALTER TABLE `work_knowledge_base_share_info` 
+--   ADD COLUMN `read_count` INT(11) NOT NULL DEFAULT 0 COMMENT '阅读量' AFTER `audit_reject_count`,
+--   ADD COLUMN `like_count` INT(11) NOT NULL DEFAULT 0 COMMENT '点赞量' AFTER `read_count`;
+
+-- 说明:
+-- 1. 阅读记录表采用累加方式,每次访问更新 last_read_time 和 read_count
+-- 2. 点赞记录表支持点赞和取消操作,通过 status 字段区分
+-- 3. 首次阅读/点赞时触发积分发放,后续操作不再发放
+-- 4. 积分规则从 work_knowledge_base_point_rule 表中读取,category_name 为"阅读"或"点赞"

+ 69 - 0
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseLikeDetail.jsp

@@ -0,0 +1,69 @@
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/webpage/include/taglib.jsp" %>
+<html>
+<head>
+    <title>点赞详情</title>
+    <meta name="decorator" content="default"/>
+    <script type="text/javascript">
+        $(document).ready(function() {
+            // 可以在这里添加初始化逻辑
+        });
+    </script>
+</head>
+<body>
+<div class="wrapper wrapper-content">
+    <div class="layui-row">
+        <div class="full-width fl">
+            <div class="contentShadow layui-form contentDetails">
+                <table class="oa-table layui-table" id="contentTable"></table>
+                <table:page page="${page}"></table:page>
+                <div style="clear:both;"></div>
+                <div style="margin-top:15px;text-align:right;color:#666;padding:10px;">
+                    <strong>总点赞量:</strong><span style="color:#ff5722;font-size:16px;">${totalCount}</span>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script src="${ctxStatic}/layer-v2.3/layui/layui.all.js" charset="utf-8"></script>
+<script>
+    layui.use('table', function(){
+        layui.table.render({
+            elem: '#contentTable'
+            ,page: false
+            ,cols: [[
+                {field:'serialNo', align:'center', title: '序号', width:80, templet: function(d){
+                    return d.serialNo || ((d.LAY_TABLE_INDEX != null ? d.LAY_TABLE_INDEX + 1 : ''));
+                }}
+                ,{field:'userName', align: 'center', title: '点赞人', minWidth: 120}
+                ,{field:'likeTime', align: 'center', title: '点赞时间', minWidth: 180}
+                ,{field:'statusText', align: 'center', title: '状态', width: 100, templet: function(d){
+                    if(d.status == 1 || d.statusText == '已点赞'){
+                        return '<span style="color:#ff5722;">❤ 已点赞</span>';
+                    } else {
+                        return '<span style="color:#999;">已取消</span>';
+                    }
+                }}
+            ]]
+            ,data: [
+                <c:choose>
+                    <c:when test="${not empty page.list}">
+                        <c:forEach items="${page.list}" var="row" varStatus="st">
+                            <c:if test="${st.index != 0}">,</c:if>
+                            {
+                                "serialNo": "${st.index + 1 + (page.pageNo - 1) * page.pageSize}"
+                                ,"userName": "<c:out value='${row.user.name}'/>"
+                                ,"likeTime": "<fmt:formatDate value='${row.likeTime}' pattern='yyyy-MM-dd HH:mm:ss'/>"
+                                ,"statusText": "${row.status == 1 ? '已点赞' : '已取消'}"
+                                ,"status": "${row.status}"
+                            }
+                        </c:forEach>
+                    </c:when>
+                    <c:otherwise></c:otherwise>
+                </c:choose>
+            ]
+        });
+    });
+</script>
+</body>
+</html>

+ 64 - 0
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseReadDetail.jsp

@@ -0,0 +1,64 @@
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/webpage/include/taglib.jsp" %>
+<html>
+<head>
+    <title>阅读详情</title>
+    <meta name="decorator" content="default"/>
+    <script type="text/javascript">
+        $(document).ready(function() {
+            // 可以在这里添加初始化逻辑
+        });
+    </script>
+</head>
+<body>
+<div class="wrapper wrapper-content">
+    <div class="layui-row">
+        <div class="full-width fl">
+            <div class="contentShadow layui-form contentDetails">
+                <table class="oa-table layui-table" id="contentTable"></table>
+                <table:page page="${page}"></table:page>
+                <div style="clear:both;"></div>
+                <div style="margin-top:15px;text-align:right;color:#666;padding:10px;">
+                    <strong>总阅读量:</strong><span style="color:#1e9fff;font-size:16px;">${totalCount}</span>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script src="${ctxStatic}/layer-v2.3/layui/layui.all.js" charset="utf-8"></script>
+<script>
+    layui.use('table', function(){
+        layui.table.render({
+            elem: '#contentTable'
+            ,page: false
+            ,cols: [[
+                {field:'serialNo', align:'center', title: '序号', width:80, templet: function(d){
+                    return d.serialNo || ((d.LAY_TABLE_INDEX != null ? d.LAY_TABLE_INDEX + 1 : ''));
+                }}
+                ,{field:'userName', align: 'center', title: '访问人', minWidth: 120}
+                ,{field:'firstReadTime', align: 'center', title: '首次访问时间', minWidth: 180}
+                ,{field:'lastReadTime', align: 'center', title: '最近访问时间', minWidth: 180}
+                ,{field:'readCount', align: 'center', title: '访问次数', width: 100}
+            ]]
+            ,data: [
+                <c:choose>
+                    <c:when test="${not empty page.list}">
+                        <c:forEach items="${page.list}" var="row" varStatus="st">
+                            <c:if test="${st.index != 0}">,</c:if>
+                            {
+                                "serialNo": "${st.index + 1 + (page.pageNo - 1) * page.pageSize}"
+                                ,"userName": "<c:out value='${row.user.name}'/>"
+                                ,"firstReadTime": "<fmt:formatDate value='${row.firstReadTime}' pattern='yyyy-MM-dd HH:mm:ss'/>"
+                                ,"lastReadTime": "<fmt:formatDate value='${row.lastReadTime}' pattern='yyyy-MM-dd HH:mm:ss'/>"
+                                ,"readCount": "${row.readCount}"
+                            }
+                        </c:forEach>
+                    </c:when>
+                    <c:otherwise></c:otherwise>
+                </c:choose>
+            ]
+        });
+    });
+</script>
+</body>
+</html>

+ 41 - 1
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseShareDetail.jsp

@@ -183,7 +183,19 @@
 
         <form:form id="inputForm" modelAttribute="entity" action="" method="post" class="layui-form">
         <div class="form-group layui-row">
-            <div class="form-group-label"><h2>知识库文件信息</h2></div>
+            <div class="form-group-label">
+                <h2 style="display:inline-block;">知识库文件信息</h2>
+                <span style="float:right; margin-right:10px; font-size:14px; color:#666;">
+                    <a href="javascript:void(0)" onclick="showReadDetail('${entity.id}')" 
+                       style="color:#1e9fff; text-decoration:none; margin-right:20px;">
+                        <i class="fa fa-eye"></i> 阅读量:<strong>${readCount != null ? readCount : 0}</strong>
+                    </a>
+                    <a href="javascript:void(0)" onclick="showLikeDetail('${entity.id}')" 
+                       style="color:#ff5722; text-decoration:none;">
+                        <i class="fa fa-heart"></i> 点赞量:<strong>${likeCount != null ? likeCount : 0}</strong>
+                    </a>
+                </span>
+            </div>
 
             <!-- 文件名称 -->
             <div class="layui-item layui-col-sm6 lw6">
@@ -435,6 +447,34 @@
 </div>
 </c:if>
 
+<script src="${ctxStatic}/layer-v2.3/layui/layui.all.js" charset="utf-8"></script>
+<script>
+    /** 显示阅读详情弹窗 */
+    function showReadDetail(fileId) {
+        top.layer.open({
+            type: 2,  // iframe类型
+            area: ['90%', '80%'],
+            title: '阅读详情',
+            maxmin: true,
+            content: '${ctx}/workKnowledgeBase/readLike/readDetailPage?fileId=' + fileId,
+            btn: ['关闭'],
+            btn1: function(index) { top.layer.close(index); }
+        });
+    }
+
+    /** 显示点赞详情弹窗 */
+    function showLikeDetail(fileId) {
+        top.layer.open({
+            type: 2,
+            area: ['80%', '80%'],
+            title: '点赞详情',
+            maxmin: true,
+            content: '${ctx}/workKnowledgeBase/readLike/likeDetailPage?fileId=' + fileId,
+            btn: ['关闭'],
+            btn1: function(index) { top.layer.close(index); }
+        });
+    }
+</script>
 
 </body>
 </html>

+ 133 - 5
src/main/webapp/webpage/modules/WorkKnowledgeBase/workKnowledgeBaseShareList.jsp

@@ -84,6 +84,50 @@
                 btn1: function(index) { top.layer.close(index); }
             });
         }
+        
+        /** 查看详情(带阅读记录) */
+        function openDetailDialogWithRead(id) {
+            // 先记录阅读行为
+            $.ajax({
+                type: 'POST',
+                url: '${ctx}/workKnowledgeBase/readLike/recordRead',
+                data: { fileId: id },
+                success: function(result) {
+                    // 无论是否首次阅读,都打开详情弹窗
+                    openDetailDialog(id);
+                    // 刷新列表,更新阅读量
+                    setTimeout(function() {
+                        search();
+                    }, 500);
+                },
+                error: function() {
+                    // 即使记录失败也打开详情
+                    openDetailDialog(id);
+                }
+            });
+        }
+        
+        /** 预览文件(带阅读记录) */
+        function openPreviewWithRead(url, type, fileId) {
+            // 先记录阅读行为
+            $.ajax({
+                type: 'POST',
+                url: '${ctx}/workKnowledgeBase/readLike/recordRead',
+                data: { fileId: fileId },
+                success: function(result) {
+                    // 调用原有的预览方法
+                    openPreview(url, type);
+                    // 刷新列表,更新阅读量
+                    setTimeout(function() {
+                        search();
+                    }, 500);
+                },
+                error: function() {
+                    // 即使记录失败也打开预览
+                    openPreview(url, type);
+                }
+            });
+        }
 
         /** 审核详情弹窗(三按钮:通过/驳回/关闭) */
         function openAuditDetailDialog(id) {
@@ -271,6 +315,72 @@
             });
         }
 
+        /** 显示阅读详情弹窗 */
+        function showReadDetail(fileId) {
+            top.layer.open({
+                type: 2,
+                area: ['90%', '80%'],
+                title: '阅读详情',
+                maxmin: true,
+                content: '${ctx}/workKnowledgeBase/readLike/readDetailPage?fileId=' + fileId,
+                btn: ['关闭'],
+                btn1: function(index) { top.layer.close(index); }
+            });
+        }
+
+        /** 显示点赞详情弹窗 */
+        function showLikeDetail(fileId) {
+            top.layer.open({
+                type: 2,
+                area: ['80%', '80%'],
+                title: '点赞详情',
+                maxmin: true,
+                content: '${ctx}/workKnowledgeBase/readLike/likeDetailPage?fileId=' + fileId,
+                btn: ['关闭'],
+                btn1: function(index) { top.layer.close(index); }
+            });
+        }
+
+        /** 点赞操作 */
+        function likeFile(fileId) {
+            $.ajax({
+                type: 'POST',
+                url: '${ctx}/workKnowledgeBase/readLike/like',
+                data: { fileId: fileId },
+                success: function(data) {
+                    if (data.success) {
+                        layer.msg('点赞成功', {icon: 1});
+                        search(); // 刷新列表
+                    } else {
+                        layer.msg(data.message || '点赞失败', {icon: 2});
+                    }
+                },
+                error: function() {
+                    layer.msg('请求失败,请重试', {icon: 2});
+                }
+            });
+        }
+
+        /** 取消点赞操作 */
+        function cancelLikeFile(fileId) {
+            $.ajax({
+                type: 'POST',
+                url: '${ctx}/workKnowledgeBase/readLike/cancelLike',
+                data: { fileId: fileId },
+                success: function(data) {
+                    if (data.success) {
+                        layer.msg('已取消点赞', {icon: 1});
+                        search(); // 刷新列表
+                    } else {
+                        layer.msg(data.message || '取消点赞失败', {icon: 2});
+                    }
+                },
+                error: function() {
+                    layer.msg('请求失败,请重试', {icon: 2});
+                }
+            });
+        }
+
         /** 查询 */
         function search() {
             $('#searchForm').submit();
@@ -460,24 +570,24 @@
         var cols = [
             {field: 'index', align: 'center', width: 50, title: '序号'},
             {field: 'name', align: 'center', title: '文件名称', minWidth: 200, templet: function(d) {
-                return '<a href="javascript:void(0)" class="attention-info" onclick="openDetailDialog(\'' + d.id + '\')">' + (d.name || '') + '</a>';
+                return '<a href="javascript:void(0)" class="attention-info" onclick="openDetailDialogWithRead(\'' + d.id + '\')">' + (d.name || '') + '</a>';
             }},
             {field: 'fileUrl', align: 'center', title: '文件地址', minWidth: 200, templet: function(d) {
                 if(2 == d.uploadMode){
                     if(d.fileName.toLowerCase().indexOf('jpg') != -1 || d.fileName.toLowerCase().indexOf('png') != -1 || d.fileName.toLowerCase().indexOf('gif') != -1 || d.fileName.toLowerCase().indexOf('bmp') != -1 || d.fileName.toLowerCase().indexOf('jpeg') != -1){
                         return "<img src="+d.temporaryUrl+" width='50' height='50' onclick=\"openDialogView('预览','${ctx}/sys/picturepreview/picturePreview?url=" + d.temporaryUrl +"','90%','90%')\" alt='"+d.fileName+"'>";
                     }else if(d.fileName.toLowerCase().indexOf('pdf') != -1){
-                        return "<a class=\"attention-info\" title=\"" + d.fileName + "\" href=\"javascript:void(0);\" onclick=\"openPreview('" + d.temporaryUrl +"',1)\">" + d.fileName + "</a>";
+                        return "<a class=\"attention-info\" title=\"" + d.fileName + "\" href=\"javascript:void(0);\" onclick=\"openPreviewWithRead('" + d.temporaryUrl +"',1,'" + d.id + "')\">" + d.fileName + "</a>";
                     }else{
-                        return "<a class=\"attention-info\" title=\"" + d.fileName + "\" href=\"javascript:void(0);\" onclick=\"openPreview('" + d.temporaryUrl +"',2)\">" + d.fileName + "</a>";
+                        return "<a class=\"attention-info\" title=\"" + d.fileName + "\" href=\"javascript:void(0);\" onclick=\"openPreviewWithRead('" + d.temporaryUrl +"',2,'" + d.id + "')\">" + d.fileName + "</a>";
                     }
                 }else{
                     if(d.fileName.toLowerCase().indexOf('jpg') != -1 || d.fileName.toLowerCase().indexOf('png') != -1 || d.fileName.toLowerCase().indexOf('gif') != -1 || d.fileName.toLowerCase().indexOf('bmp') != -1 || d.fileName.toLowerCase().indexOf('jpeg') != -1){
                         return "<img src="+d.fileUrl+" width='50' height='50' onclick=\"openDialogView('预览','${ctx}/sys/picturepreview/picturePreview?url=" + d.fileUrl +"','90%','90%')\" alt='"+d.fileName+"'>";
                     }else if(d.fileName.toLowerCase().indexOf('pdf') != -1){
-                        return "<a class=\"attention-info\" title=\"" + d.fileName + "\" href=\"javascript:void(0);\" onclick=\"openPreview('" + d.fileUrl +"',1)\">" + d.fileName + "</a>";
+                        return "<a class=\"attention-info\" title=\"" + d.fileName + "\" href=\"javascript:void(0);\" onclick=\"openPreviewWithRead('" + d.fileUrl +"',1,'" + d.id + "')\">" + d.fileName + "</a>";
                     }else{
-                        return "<a class=\"attention-info\" title=\"" + d.fileName + "\" href=\"javascript:void(0);\" onclick=\"openPreview('" + d.fileUrl +"',2)\">" + d.fileName + "</a>";
+                        return "<a class=\"attention-info\" title=\"" + d.fileName + "\" href=\"javascript:void(0);\" onclick=\"openPreviewWithRead('" + d.fileUrl +"',2,'" + d.id + "')\">" + d.fileName + "</a>";
                     }
                 }
             }},
@@ -497,6 +607,12 @@
                 }},
             {field: 'createByName', align: 'center', title: '创建人', width: 100},
             {field: 'createTime', align: 'center', title: '创建时间', width: 160},
+            {field: 'readCount', align: 'center', title: '阅读量', width: 100, templet: function(d){
+                return '<a href="javascript:void(0)" onclick="showReadDetail(\'' + d.id + '\')" style="color:#1e9fff;cursor:pointer;">' + (d.readCount || 0) + '</a>';
+            }},
+            {field: 'likeCount', align: 'center', title: '点赞量', width: 100, templet: function(d){
+                return '<a href="javascript:void(0)" onclick="showLikeDetail(\'' + d.id + '\')" style="color:#ff5722;cursor:pointer;">' + (d.likeCount || 0) + '</a>';
+            }},
 
             {align:'center', title: '状态', fixed: 'right', width:70,templet:function(d){
                     var st = getWorkKnowledgeAuditState(parseInt(d.auditStatus));
@@ -547,6 +663,15 @@
                 if (status === 1 || status === 2 || status === 4 || status === 5) {
                     xml += '<a href="javascript:void(0)" onclick="viewAuditRecords(\'' + d.id + '\')" class="layui-btn layui-btn-xs"> 记录</a>';
                 }
+                
+                // 点赞/取消点赞按钮(仅审核通过的文件显示)
+                if (status === 5) {
+                    if (d.liked) {
+                        xml += '<a href="javascript:void(0)" onclick="cancelLikeFile(\'' + d.id + '\')" class="layui-btn layui-btn-xs layui-bg-orange"><i class="fa fa-heart"></i> 已赞</a>';
+                    } else {
+                        xml += '<a href="javascript:void(0)" onclick="likeFile(\'' + d.id + '\')" class="layui-btn layui-btn-xs layui-bg-red"><i class="fa fa-heart-o"></i> 点赞</a>';
+                    }
+                }
             
                 xml += '</div>';
                 return xml;
@@ -584,6 +709,9 @@
                     ,"auditStatus": ${row.auditStatus != null ? row.auditStatus : 0}
                     ,"treeNodeId": "${row.treeNodeId}"
                     ,"treeName": "<c:out value='${row.treeName}'/>"
+                    ,"readCount": ${row.readCount != null ? row.readCount : 0}
+                    ,"likeCount": ${row.likeCount != null ? row.likeCount : 0}
+                    ,"liked": ${row.liked == true ? 'true' : 'false'}
                     <c:forEach items="${dynamicFields}" var="field">
                     ,"dynamic_${field.fieldKey}": "<c:out value='${row["dynamic_".concat(field.fieldKey)]}'/>"