ct преди 2 дни
родител
ревизия
5cc975594d

+ 3 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java

@@ -7,6 +7,7 @@ import com.fs.course.param.FsUserCourseVideoListUParam;
 import com.fs.course.param.FsUserCourseVideoParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
 import com.fs.course.vo.FsCourseVideoListBySidebarVO;
+import com.fs.course.vo.FsUserCourseVO;
 import com.fs.course.vo.FsUserCourseVideoListUVO;
 import com.fs.course.vo.FsUserCourseVideoVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
@@ -34,6 +35,8 @@ public interface FsUserCourseVideoMapper
      */
     public FsUserCourseVideo selectFsUserCourseVideoByVideoId(Long videoId);
 
+    FsUserCourseVO selectFsUserCourseVideoVoByVideoId(@Param("videoId") Long videoId, @Param("periodId") Long periodId);
+
     /**
      * 查询课堂视频列表
      *

+ 1 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -976,7 +976,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         packetParam.setRedPacketMode(config.getRedPacketMode());
         packetParam.setCompanyId(param.getCompanyId());
 
-        System.out.println("红包商户号"+amount);
+        System.out.println("红包金额"+amount);
         System.out.println("红包商户号"+packetParam);
         //2025.6.19 红包金额为0的时候
         if (amount.compareTo(BigDecimal.ZERO)>0){

+ 40 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCourseVO.java

@@ -0,0 +1,40 @@
+package com.fs.course.vo;
+
+import com.fs.common.core.domain.BaseEntity;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+
+@Data
+public class FsUserCourseVO extends BaseEntity {
+
+    @ApiModelProperty(value = "课程视频id")
+    private Long videoId;
+
+    @ApiModelProperty(value = "视频标题")
+    private String title;
+
+
+    @ApiModelProperty(value = "课程ID")
+    private Long courseId;
+
+
+    @ApiModelProperty(value = "课程名称")
+    private String courseName;
+
+
+    @ApiModelProperty(value = "营期id")
+    private Long periodId;
+
+    @ApiModelProperty(value = "营期id")
+    private String periodName;
+
+    @ApiModelProperty(value = "训练营id")
+    private Long trainingCampId;
+
+    @ApiModelProperty(value = "训练营名称")
+    private String trainingCampName;
+
+
+
+}

+ 37 - 0
fs-service/src/main/java/com/fs/his/domain/FsUserOperationLog.java

@@ -0,0 +1,37 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 用户操作日志对象 fs_user_operation_log
+ *
+ * @author fs
+ * @date 2025-07-04
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsUserOperationLog extends BaseEntity{
+
+    private Long logId;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 操作类型 */
+    @Excel(name = "操作类型")
+    private String operationType;
+
+    /** 详情 */
+    @Excel(name = "详情")
+    private String details;
+
+    @Excel(name = "参数")
+    private String param;
+
+
+}

+ 35 - 0
fs-service/src/main/java/com/fs/his/enums/FsUserOperationEnum.java

@@ -0,0 +1,35 @@
+package com.fs.his.enums;
+
+import java.util.stream.Stream;
+
+public enum FsUserOperationEnum {
+    MINLOGIN("小程序登录",1),
+    H5LOGIN("h5登录",2),
+    BEMEMBER("成为会员",3),
+    ISADDKF("判断是否成为会员",4),
+    STUDY("学习课程",5),
+    ANSWER("答题",6),
+    SENDREWARD("发送奖励",7);
+
+    private final String label;
+    private final Integer value;
+
+    FsUserOperationEnum(String label, Integer value) {
+        this.label = label;
+        this.value = value;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public Integer getValue() {
+        return value;
+    }
+    public static FsUserOperationEnum toType(String label) {
+        return Stream.of(FsUserOperationEnum.values())
+                .filter(p -> p.label.equals(label))
+                .findAny()
+                .orElse(null);
+    }
+}

+ 61 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserOperationLogMapper.java

@@ -0,0 +1,61 @@
+package com.fs.his.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsUserOperationLog;
+
+/**
+ * 用户操作日志Mapper接口
+ * 
+ * @author fs
+ * @date 2025-07-04
+ */
+public interface FsUserOperationLogMapper extends BaseMapper<FsUserOperationLog>{
+    /**
+     * 查询用户操作日志
+     * 
+     * @param logId 用户操作日志主键
+     * @return 用户操作日志
+     */
+    FsUserOperationLog selectFsUserOperationLogByLogId(Long logId);
+
+    /**
+     * 查询用户操作日志列表
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 用户操作日志集合
+     */
+    List<FsUserOperationLog> selectFsUserOperationLogList(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 新增用户操作日志
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 结果
+     */
+    int insertFsUserOperationLog(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 修改用户操作日志
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 结果
+     */
+    int updateFsUserOperationLog(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 删除用户操作日志
+     * 
+     * @param logId 用户操作日志主键
+     * @return 结果
+     */
+    int deleteFsUserOperationLogByLogId(Long logId);
+
+    /**
+     * 批量删除用户操作日志
+     * 
+     * @param logIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsUserOperationLogByLogIds(Long[] logIds);
+}

+ 61 - 0
fs-service/src/main/java/com/fs/his/service/IFsUserOperationLogService.java

@@ -0,0 +1,61 @@
+package com.fs.his.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsUserOperationLog;
+
+/**
+ * 用户操作日志Service接口
+ * 
+ * @author fs
+ * @date 2025-07-04
+ */
+public interface IFsUserOperationLogService extends IService<FsUserOperationLog>{
+    /**
+     * 查询用户操作日志
+     * 
+     * @param logId 用户操作日志主键
+     * @return 用户操作日志
+     */
+    FsUserOperationLog selectFsUserOperationLogByLogId(Long logId);
+
+    /**
+     * 查询用户操作日志列表
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 用户操作日志集合
+     */
+    List<FsUserOperationLog> selectFsUserOperationLogList(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 新增用户操作日志
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 结果
+     */
+    int insertFsUserOperationLog(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 修改用户操作日志
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 结果
+     */
+    int updateFsUserOperationLog(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 批量删除用户操作日志
+     * 
+     * @param logIds 需要删除的用户操作日志主键集合
+     * @return 结果
+     */
+    int deleteFsUserOperationLogByLogIds(Long[] logIds);
+
+    /**
+     * 删除用户操作日志信息
+     * 
+     * @param logId 用户操作日志主键
+     * @return 结果
+     */
+    int deleteFsUserOperationLogByLogId(Long logId);
+}

+ 93 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsUserOperationLogServiceImpl.java

@@ -0,0 +1,93 @@
+package com.fs.his.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.his.mapper.FsUserOperationLogMapper;
+import com.fs.his.domain.FsUserOperationLog;
+import com.fs.his.service.IFsUserOperationLogService;
+
+/**
+ * 用户操作日志Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-07-04
+ */
+@Service
+public class FsUserOperationLogServiceImpl extends ServiceImpl<FsUserOperationLogMapper, FsUserOperationLog> implements IFsUserOperationLogService {
+
+    /**
+     * 查询用户操作日志
+     * 
+     * @param logId 用户操作日志主键
+     * @return 用户操作日志
+     */
+    @Override
+    public FsUserOperationLog selectFsUserOperationLogByLogId(Long logId)
+    {
+        return baseMapper.selectFsUserOperationLogByLogId(logId);
+    }
+
+    /**
+     * 查询用户操作日志列表
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 用户操作日志
+     */
+    @Override
+    public List<FsUserOperationLog> selectFsUserOperationLogList(FsUserOperationLog fsUserOperationLog)
+    {
+        return baseMapper.selectFsUserOperationLogList(fsUserOperationLog);
+    }
+
+    /**
+     * 新增用户操作日志
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 结果
+     */
+    @Override
+    public int insertFsUserOperationLog(FsUserOperationLog fsUserOperationLog)
+    {
+        fsUserOperationLog.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsUserOperationLog(fsUserOperationLog);
+    }
+
+    /**
+     * 修改用户操作日志
+     * 
+     * @param fsUserOperationLog 用户操作日志
+     * @return 结果
+     */
+    @Override
+    public int updateFsUserOperationLog(FsUserOperationLog fsUserOperationLog)
+    {
+        return baseMapper.updateFsUserOperationLog(fsUserOperationLog);
+    }
+
+    /**
+     * 批量删除用户操作日志
+     * 
+     * @param logIds 需要删除的用户操作日志主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserOperationLogByLogIds(Long[] logIds)
+    {
+        return baseMapper.deleteFsUserOperationLogByLogIds(logIds);
+    }
+
+    /**
+     * 删除用户操作日志信息
+     * 
+     * @param logId 用户操作日志主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserOperationLogByLogId(Long logId)
+    {
+        return baseMapper.deleteFsUserOperationLogByLogId(logId);
+    }
+}

+ 19 - 0
fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml

@@ -310,6 +310,25 @@
         </if>
         order by video.course_sort
     </select>
+    <select id="selectFsUserCourseVideoVoByVideoId" resultType="com.fs.course.vo.FsUserCourseVO">
+        select
+        video.video_id,
+        video.title,
+        course.course_id,
+        course.course_name,
+        fcp.period_id,
+        fcp.period_name,
+        c.training_camp_id,
+        c.training_camp_name
+        from `fs_user_course_video` video
+        left join fs_user_course course ON video.course_id = course.course_id
+        left join fs_user_course_period_days fcpd on fcpd.video_id = video.video_id
+        left join fs_user_course_period fcp on fcp.period_id = fcpd.period_id
+        left join fs_user_course_training_camp c on fcp.training_camp_id = c.training_camp_id
+        where course.is_del = 0 and video.video_id = #{videoId}
+         and fcp.period_id = #{periodId}
+
+    </select>
 
     <update id="updateRedPacketMoney">
         update fs_user_course_video set red_packet_money = #{redPacketMoney} where video_id = #{videoId}

+ 76 - 0
fs-service/src/main/resources/mapper/his/FsUserOperationLogMapper.xml

@@ -0,0 +1,76 @@
+<?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.fs.his.mapper.FsUserOperationLogMapper">
+
+    <resultMap type="FsUserOperationLog" id="FsUserOperationLogResult">
+        <result property="logId"    column="log_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="operationType"    column="operation_type"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="details"    column="details"    />
+        <result property="param"    column="param"    />
+    </resultMap>
+
+    <sql id="selectFsUserOperationLogVo">
+        select log_id, user_id, operation_type, create_time, details,param from fs_user_operation_log
+    </sql>
+
+    <select id="selectFsUserOperationLogList" parameterType="FsUserOperationLog" resultMap="FsUserOperationLogResult">
+        <include refid="selectFsUserOperationLogVo"/>
+        <where>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="operationType != null  and operationType != ''"> and operation_type = #{operationType}</if>
+            <if test="details != null  and details != ''"> and details = #{details}</if>
+        </where>
+    </select>
+
+    <select id="selectFsUserOperationLogByLogId" parameterType="Long" resultMap="FsUserOperationLogResult">
+        <include refid="selectFsUserOperationLogVo"/>
+        where log_id = #{logId}
+    </select>
+
+    <insert id="insertFsUserOperationLog" parameterType="FsUserOperationLog">
+        insert into fs_user_operation_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="logId != null">log_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="operationType != null and operationType != ''">operation_type,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="details != null">details,</if>
+            <if test="param != null">param,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="logId != null">#{logId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="operationType != null and operationType != ''">#{operationType},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="details != null">#{details},</if>
+            <if test="param != null">#{param},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsUserOperationLog" parameterType="FsUserOperationLog">
+        update fs_user_operation_log
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="operationType != null and operationType != ''">operation_type = #{operationType},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="details != null">details = #{details},</if>
+            <if test="param != null and param != ''">param = #{param},</if>
+        </trim>
+        where log_id = #{logId}
+    </update>
+
+    <delete id="deleteFsUserOperationLogByLogId" parameterType="Long">
+        delete from fs_user_operation_log where log_id = #{logId}
+    </delete>
+
+    <delete id="deleteFsUserOperationLogByLogIds" parameterType="String">
+        delete from fs_user_operation_log where log_id in
+        <foreach item="logId" collection="array" open="(" separator="," close=")">
+            #{logId}
+        </foreach>
+    </delete>
+</mapper>

+ 18 - 0
fs-user-app/src/main/java/com/fs/app/annotation/UserOperationLog.java

@@ -0,0 +1,18 @@
+package com.fs.app.annotation;
+
+
+import com.fs.his.enums.FsUserOperationEnum;
+
+import java.lang.annotation.*;
+
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface UserOperationLog {
+    /** 操作类型,比如 "新增", "删除", "更新", "查询" */
+    FsUserOperationEnum operationType();
+
+    /** 操作详情(可选) */
+    String detail() default "";
+}

+ 5 - 0
fs-user-app/src/main/java/com/fs/app/controller/CourseWxH5Controller.java

@@ -2,6 +2,7 @@ package com.fs.app.controller;
 
 
 import com.fs.app.annotation.Login;
+import com.fs.app.annotation.UserOperationLog;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
@@ -14,6 +15,7 @@ import com.fs.course.param.newfs.FsUserCourseVideoUParam;
 import com.fs.course.service.*;
 import com.fs.course.vo.FsUserCourseVideoH5VO;
 import com.fs.course.vo.newfs.FsUserCourseVideoLinkDetailsVO;
+import com.fs.his.enums.FsUserOperationEnum;
 import com.fs.his.service.IFsUserService;
 import com.fs.system.service.ISysConfigService;
 import io.swagger.annotations.Api;
@@ -72,6 +74,7 @@ public class CourseWxH5Controller extends AppBaseController {
     @Login
     @ApiOperation("H5课程详情")
     @GetMapping("/videoDetails")
+    @UserOperationLog(operationType = FsUserOperationEnum.STUDY)
     public ResponseResult<FsUserCourseVideoLinkDetailsVO> getCourseVideoDetails(FsUserCourseVideoLinkParam param) {
         param.setFsUserId(Long.parseLong(getUserId()));
         return courseVideoService.getLinkCourseVideoDetails(param);
@@ -103,6 +106,7 @@ public class CourseWxH5Controller extends AppBaseController {
 
     @ApiOperation("答题")
     @PostMapping("/courseAnswer")
+    @UserOperationLog(operationType = FsUserOperationEnum.ANSWER)
     public R courseAnswer(@RequestBody FsCourseQuestionAnswerUParam param){
         param.setUserId(Long.parseLong(getUserId()));
         logger.info("zyp \n【答题】:{}",param.getQuestions());
@@ -115,6 +119,7 @@ public class CourseWxH5Controller extends AppBaseController {
     @ApiOperation("发放奖励")
     @PostMapping("/sendReward")
     @RepeatSubmit
+    @UserOperationLog(operationType = FsUserOperationEnum.SENDREWARD)
     public R sendReward(@RequestBody FsCourseSendRewardUParam param)
     {
         param.setUserId(Long.parseLong(getUserId()));

+ 3 - 0
fs-user-app/src/main/java/com/fs/app/controller/WxCompanyUserController.java

@@ -5,6 +5,7 @@ import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
 import cn.hutool.core.date.DateTime;
 import com.alibaba.fastjson.JSON;
+import com.fs.app.annotation.UserOperationLog;
 import com.fs.app.utils.JwtUtils;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
@@ -19,6 +20,7 @@ import com.fs.company.service.ICompanyUserService;
 import com.fs.core.config.WxMaConfiguration;
 import com.fs.course.config.CourseMaConfig;
 import com.fs.his.domain.FsUser;
+import com.fs.his.enums.FsUserOperationEnum;
 import com.fs.his.service.IFsUserService;
 
 import com.fs.system.domain.SysConfig;
@@ -74,6 +76,7 @@ public class WxCompanyUserController extends AppBaseController {
 
     @ApiOperation("小程序-授权登录")
     @PostMapping("/loginByMa")
+    @UserOperationLog(operationType = FsUserOperationEnum.MINLOGIN)
     public R login(@RequestBody LoginMaWxParam param) {
         log.info("=====================进入小程序授权登录, 入参: {}", param);
         if (StringUtils.isBlank(param.getCode())) {

+ 3 - 0
fs-user-app/src/main/java/com/fs/app/controller/WxMpController.java

@@ -1,6 +1,7 @@
 package com.fs.app.controller;
 
 import cn.hutool.core.date.DateTime;
+import com.fs.app.annotation.UserOperationLog;
 import com.fs.app.param.FsUserLoginByMpParam;
 import com.fs.app.utils.JwtUtils;
 import com.fs.common.core.domain.R;
@@ -8,6 +9,7 @@ import com.fs.common.core.redis.RedisCache;
 import com.fs.course.mapper.FsCourseSopLogsMapper;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.his.domain.FsUser;
+import com.fs.his.enums.FsUserOperationEnum;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.qw.mapper.QwExternalContactMapper;
@@ -157,6 +159,7 @@ public class WxMpController {
   @ApiOperation("课程短链公众号登录")
   @PostMapping("/loginByMp")
   @Transactional
+  @UserOperationLog(operationType = FsUserOperationEnum.H5LOGIN)
   public R loginByMp( @RequestBody FsUserLoginByMpParam param) {
 
     if (StringUtils.isBlank(param.getCode())) {

+ 283 - 0
fs-user-app/src/main/java/com/fs/framework/aspectj/UserOperationLogAspect.java

@@ -0,0 +1,283 @@
+package com.fs.framework.aspectj;
+
+import com.alibaba.druid.support.json.JSONUtils;
+import com.alibaba.fastjson.JSON;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.app.annotation.UserOperationLog;
+import com.fs.app.utils.JwtUtils;
+import com.fs.common.core.domain.R;
+import com.fs.common.param.LoginMaWxParam;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.course.domain.FsCourseQuestionBank;
+import com.fs.course.mapper.FsUserCourseVideoMapper;
+import com.fs.course.param.FsCourseQuestionAnswerUParam;
+import com.fs.course.param.FsCourseSendRewardUParam;
+import com.fs.course.param.newfs.FsUserCourseVideoLinkParam;
+import com.fs.course.vo.FsUserCourseVO;
+import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
+import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserOperationLog;
+import com.fs.his.enums.FsUserOperationEnum;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.mapper.FsUserOperationLogMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.*;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Aspect
+@Component
+public class UserOperationLogAspect {
+
+    @Autowired
+    private FsUserOperationLogMapper logMapper;
+    @Autowired
+    private FsUserMapper userMapper;
+    @Autowired
+    private FsUserCourseVideoMapper userCourseVideoMapper;
+    @Autowired
+    JwtUtils jwtUtils;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+    private static final ThreadLocal<FsUserOperationLog> LOG_HOLDER = new ThreadLocal<>();
+
+    @Pointcut("@annotation(com.fs.app.annotation.UserOperationLog)")
+    public void logPointcut() {}
+
+
+    @AfterReturning(pointcut = "logPointcut()", returning = "result")
+    public void doAfterReturning(JoinPoint joinPoint, Object result) {
+        handleLog(joinPoint, null);
+    }
+
+    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
+        handleLog(joinPoint, e);
+    }
+
+    @AfterReturning(pointcut = "@annotation(userOpLog)", returning = "result")
+    public void afterReturning(JoinPoint joinPoint,UserOperationLog userOpLog, Object result) {
+        FsUserOperationLog operationLog = LOG_HOLDER.get();
+        if (operationLog == null) return;
+
+        try {
+            if (operationLog.getUserId() == null){
+                FsUser fsUser = extractUserFromResult(result);
+                if (fsUser != null && operationLog.getUserId() == null) {
+                    operationLog.setUserId(fsUser.getUserId());
+                    StringBuilder details = getDetail(
+                            userOpLog,
+                            FsUserOperationEnum.toType(operationLog.getOperationType()),
+                            fsUser,
+                            joinPoint.getArgs());
+                    operationLog.setDetails(details.toString());
+                }
+            }
+            logMapper.insertFsUserOperationLog(operationLog);
+        } catch (Exception ex) {
+            log.error("操作日志插入异常", ex);
+        } finally {
+            LOG_HOLDER.remove();
+        }
+    }
+
+    private void handleLog(JoinPoint joinPoint, Exception e) {
+        FsUserOperationLog operationLog = new FsUserOperationLog();
+        LOG_HOLDER.set(operationLog);
+        try {
+            //时间
+            operationLog.setCreateTime(DateUtils.getNowDate());
+            //操作类型
+            Method method = getMethod(joinPoint);
+            UserOperationLog annotation = method.getAnnotation(UserOperationLog.class);
+            if (annotation == null) return;
+            operationLog.setOperationType(annotation.operationType().getLabel());
+
+            //用户
+            Long userId =null;
+            try {
+                userId = Long.valueOf(jwtUtils.getClaimByToken(ServletUtils.getRequest().getHeader("APPToken")).getSubject().toString());
+            } catch (Exception ie){
+                log.info("获取用户id失败");
+            }
+            if (userId == null) {
+                LOG_HOLDER.set(operationLog);
+                return;
+            }
+
+            FsUser fsUser = userMapper.selectFsUserByUserId(userId);
+            if (fsUser == null) {
+                LOG_HOLDER.set(operationLog);
+                return;
+            }
+            operationLog.setUserId(userId);
+
+
+            StringBuilder details = getDetail(annotation, annotation.operationType(), fsUser,joinPoint.getArgs());
+            operationLog.setDetails(details.toString());
+
+            if (e != null) {
+                details.append(",异常: ").append(e.getMessage());
+            }
+
+
+            LOG_HOLDER.set(operationLog);
+        } catch (Exception ex) {
+            log.error("记录操作日志异常", ex);
+        }
+    }
+
+    private StringBuilder getDetail(UserOperationLog annotation, FsUserOperationEnum opType, FsUser fsUser,Object[] args) {
+        StringBuilder details = new StringBuilder();
+        if (annotation.detail() == null || annotation.detail().isEmpty()) {
+            switch (opType.getValue()) {
+                case 1: // 小程序登录
+                    details.append(fsUser.getNickName())
+                            .append("在")
+                            .append(DateUtils.getTime())
+                            .append("登录了小程序");
+                    break;
+                case 2: // h5登录
+                    details.append(fsUser.getNickName())
+                            .append("在")
+                            .append(DateUtils.getTime())
+                            .append("登录了h5");
+                    break;
+                case 3: // 成为会员
+                    details.append(fsUser.getNickName())
+                            .append("在")
+                            .append(DateUtils.getTime())
+                            .append("注册成为会员");
+                    break;
+                case 4: // 判断是否成为会员
+                    break;
+                case 5: // 学习课程
+                    String courseInfo = extractCourseInfo(args);
+                    details.append(fsUser.getNickName())
+                            .append("在")
+                            .append(DateUtils.getTime())
+                            .append("观看 ").append(courseInfo);
+                    break;
+                case 6: // 答题
+                    String answerCourse = answerCourse(args);
+                    details.append(fsUser.getNickName())
+                            .append("在 ")
+                            .append(DateUtils.getTime())
+                            .append("\n")
+                            .append(answerCourse);
+                    break;
+                case 7: // 发送奖励
+                    String sendReward = sendReward(args);
+
+                    details.append(fsUser.getNickName())
+                            .append("在 ")
+                            .append(DateUtils.getTime())
+                            .append("领取红包")
+                            .append("\n")
+                            .append(sendReward);
+                    break;
+                default:
+                    details.append(opType.getLabel());
+                    break;
+            }
+        } else {
+            details.append(annotation.detail());
+        }
+        return details;
+    }
+
+    private Method getMethod(JoinPoint joinPoint) throws NoSuchMethodException {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        return joinPoint.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
+    }
+
+
+    private FsUser extractUserFromResult(Object result) {
+        if (result instanceof R) {
+            R r = (R) result;
+            Object user = r.get("user");
+            if (user instanceof FsUser) {
+                return (FsUser) user;
+            }
+        }
+        return null;
+    }
+    private String extractCourseInfo(Object[] args) {
+        if (args == null) return "未知课程";
+
+        for (Object arg : args) {
+            if (arg instanceof FsUserCourseVideoLinkParam) {
+                FsUserCourseVideoLinkParam param = (FsUserCourseVideoLinkParam) arg;
+                if (param.getVideoId() != null && param.getPeriodId() != null) {
+                    FsUserCourseVO vo = userCourseVideoMapper.selectFsUserCourseVideoVoByVideoId(param.getVideoId(), param.getPeriodId());
+                    if (vo != null){
+                        FsUserOperationLog operationLog = LOG_HOLDER.get();
+                        operationLog.setParam(JSON.toJSONString(vo));
+                        LOG_HOLDER.set(operationLog);
+                        return "课程:"+ vo.getCourseName() + " 的 " + vo.getTitle() + " 小节";
+                    }
+                }
+            }
+        }
+        return "未知课程";
+    }
+
+    private String answerCourse(Object[] args) {
+        if (args == null) return "未知课程";
+        for (Object arg : args) {
+            if (arg instanceof FsCourseQuestionAnswerUParam) {
+                FsCourseQuestionAnswerUParam param = (FsCourseQuestionAnswerUParam) arg;
+                if (param.getVideoId() != null && param.getPeriodId() != null) {
+                    FsUserCourseVO vo = userCourseVideoMapper.selectFsUserCourseVideoVoByVideoId(param.getVideoId(), param.getPeriodId());
+                    if (vo != null){
+                        FsUserOperationLog operationLog = LOG_HOLDER.get();
+                        operationLog.setParam(JSON.toJSONString(vo));
+                        LOG_HOLDER.set(operationLog);
+                        StringBuilder details =new StringBuilder("课程:"+ vo.getCourseName() + " 的 " + vo.getTitle() + " 小节 问题为:");
+                        List<FsCourseQuestionBank> questions = param.getQuestions();
+                        if (questions != null && !questions.isEmpty()) {
+                            for (int i = 0; i < questions.size(); i++) {
+                                details.append("\n").append(i+1).append(".").append(questions.get(i).getTitle());
+                                details.append(" 提交答案为:").append(questions.get(i).getAnswer());
+                            }
+                        }
+                        return details.toString();
+                    }
+                }
+            }
+        }
+        return "未知课程";
+    }
+
+    private String sendReward(Object[] args) {
+        if (args == null) return "未知课程";
+        for (Object arg : args) {
+            if (arg instanceof FsCourseSendRewardUParam) {
+                FsCourseSendRewardUParam param = (FsCourseSendRewardUParam) arg;
+                if (param.getVideoId() != null && param.getPeriodId() != null) {
+                    FsUserCourseVO vo = userCourseVideoMapper.selectFsUserCourseVideoVoByVideoId(param.getVideoId(), param.getPeriodId());
+                    if (vo != null){
+                        FsUserOperationLog operationLog = LOG_HOLDER.get();
+                        operationLog.setParam(JSON.toJSONString(vo));
+                        LOG_HOLDER.set(operationLog);
+                        StringBuilder details =new StringBuilder();
+                        details.append("课程:"+ vo.getCourseName() + " 的 " + vo.getTitle() + " 小节");
+                        return details.toString();
+                    }
+                }
+            }
+        }
+        return "未知课程";
+    }
+
+}