浏览代码

ht

master
fzzj 10 个月前
父节点
当前提交
210c3c110f
共有 45 个文件被更改,包括 1242 次插入0 次删除
  1. 1
    0
      RuoYi-Vue/pom.xml
  2. 2
    0
      RuoYi-Vue/ruoyi-admin/src/main/resources/application.yml
  3. 44
    0
      RuoYi-Vue/ruoyi-novel/pom.xml
  4. 36
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/config/PhpDataSyncTask.java
  5. 58
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/config/ScheduledTasks.java
  6. 4
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/config/ThreadPoolConfig.java
  7. 25
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/controller/ChapterController.java
  8. 4
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/controller/NovelAdController.java
  9. 29
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/controller/NovelController.java
  10. 26
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/controller/NovelFinanceController.java
  11. 43
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/controller/NovelVoteController.java
  12. 23
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/controller/ReadingHistoryController.java
  13. 31
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/AdLog.java
  14. 13
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/AdPlatformConfig.java
  15. 21
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/Novel.java
  16. 11
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/NovelChapter.java
  17. 7
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/NovelContent.java
  18. 26
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/RoyaltyReport.java
  19. 20
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/RoyaltySummary.java
  20. 17
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/StoryOption.java
  21. 26
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/VoteRecord.java
  22. 14
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/VoteRequest.java
  23. 12
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/mapper/AdLogMapper.java
  24. 12
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/mapper/NovelChapterMapper.java
  25. 10
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/mapper/NovelMapper.java
  26. 18
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/mapper/RoyaltySummaryMapper.java
  27. 16
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/mapper/VoteRecordMapper.java
  28. 8
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/IAdService.java
  29. 9
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/IFinanceService.java
  30. 9
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/IVoteService.java
  31. 4
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/NovelSearchService.java
  32. 92
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/impl/AdServiceImpl.java
  33. 22
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/impl/ChapterServiceImpl.java
  34. 110
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/impl/FinanceServiceImpl.java
  35. 16
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/impl/NovelSearchServiceImpl.java
  36. 25
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/impl/NovelServiceImpl.java
  37. 151
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/impl/VoteServiceImpl.java
  38. 80
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/utils/AdPlatformClient.java
  39. 47
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/utils/JwtUtil.java
  40. 15
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/utils/NovelUtils.java
  41. 15
    0
      RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/vip/VipAccessAspect.java
  42. 26
    0
      RuoYi-Vue/ruoyi-novel/src/main/resources/application.yml
  43. 27
    0
      RuoYi-Vue/ruoyi-novel/src/main/resources/mapper/novel/AdLogMapper.xml
  44. 17
    0
      RuoYi-Vue/ruoyi-novel/src/main/resources/mapper/novel/RoyaltySummaryMapper.xml
  45. 20
    0
      RuoYi-Vue/ruoyi-novel/src/main/resources/mapper/novel/VoteRecordMapper.xml

+ 1
- 0
RuoYi-Vue/pom.xml 查看文件

@@ -228,6 +228,7 @@
228 228
         <module>ruoyi-quartz</module>
229 229
         <module>ruoyi-generator</module>
230 230
         <module>ruoyi-common</module>
231
+        <module>ruoyi-novel</module>
231 232
     </modules>
232 233
     <packaging>pom</packaging>
233 234
 

+ 2
- 0
RuoYi-Vue/ruoyi-admin/src/main/resources/application.yml 查看文件

@@ -65,6 +65,8 @@ spring:
65 65
     restart:
66 66
       # 热部署开关
67 67
       enabled: true
68
+  session:
69
+    store-type: redis
68 70
   # redis 配置
69 71
   redis:
70 72
     # 地址

+ 44
- 0
RuoYi-Vue/ruoyi-novel/pom.xml 查看文件

@@ -0,0 +1,44 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xmlns="http://maven.apache.org/POM/4.0.0"
3
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
+    <modelVersion>4.0.0</modelVersion>
6
+    <parent>
7
+        <groupId>com.ruoyi</groupId>
8
+        <artifactId>ruoyi</artifactId>
9
+        <version>3.9.0</version>
10
+    </parent>
11
+
12
+    <artifactId>ruoyi-novel</artifactId>
13
+
14
+    <properties>
15
+        <maven.compiler.source>17</maven.compiler.source>
16
+        <maven.compiler.target>17</maven.compiler.target>
17
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
18
+    </properties>
19
+    <dependencies>
20
+        <dependency>
21
+            <groupId>org.mybatis</groupId>
22
+            <artifactId>mybatis</artifactId>
23
+            <version>3.5.3</version>
24
+            <scope>compile</scope>
25
+        </dependency>
26
+        <dependency>
27
+            <groupId>org.projectlombok</groupId>
28
+            <artifactId>lombok</artifactId>
29
+        </dependency>
30
+        <dependency>
31
+            <groupId>org.springframework</groupId>
32
+            <artifactId>spring-context</artifactId>
33
+        </dependency>
34
+        <dependency>
35
+            <groupId>org.springframework</groupId>
36
+            <artifactId>spring-web</artifactId>
37
+        </dependency>
38
+        <dependency>
39
+            <groupId>com.ruoyi</groupId>
40
+            <artifactId>ruoyi-common</artifactId>
41
+        </dependency>
42
+    </dependencies>
43
+
44
+</project>

+ 36
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/config/PhpDataSyncTask.java 查看文件

@@ -0,0 +1,36 @@
1
+package com.ruoyi.novel.config;
2
+
3
+import com.ruoyi.novel.domain.RoyaltySummary;
4
+import com.ruoyi.novel.mapper.RoyaltySummaryMapper;
5
+import lombok.Value;
6
+import org.springframework.beans.factory.annotation.Autowired;
7
+import org.springframework.scheduling.annotation.Scheduled;
8
+import org.springframework.stereotype.Component;
9
+import org.springframework.web.client.RestTemplate;
10
+
11
+import java.util.Arrays;
12
+
13
+// 定时同步PHP数据任务
14
+@Component
15
+public class PhpDataSyncTask {
16
+
17
+    @Autowired
18
+    private RoyaltySummaryMapper royaltySummaryMapper;
19
+
20
+    @Value("${php.data.url}")
21
+    private String phpDataUrl;
22
+
23
+    // 每天凌晨2点执行
24
+    @Scheduled(cron = "0 0 2 * * ?")
25
+    public void syncRoyaltyData() {
26
+        RestTemplate restTemplate = new RestTemplate();
27
+        RoyaltySummary[] summaries = restTemplate.getForObject(
28
+                phpDataUrl + "/royalty/summary",
29
+                RoyaltySummary[].class
30
+        );
31
+
32
+        if (summaries != null) {
33
+            Arrays.stream(summaries).forEach(royaltySummaryMapper::insertOrUpdate);
34
+        }
35
+    }
36
+}

+ 58
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/config/ScheduledTasks.java 查看文件

@@ -0,0 +1,58 @@
1
+package com.ruoyi.novel.config;
2
+
3
+import com.ruoyi.novel.service.IAdService;
4
+import com.ruoyi.novel.service.IFinanceService;
5
+import com.ruoyi.novel.service.IVoteService;
6
+import org.springframework.beans.factory.annotation.Autowired;
7
+import org.springframework.context.annotation.Configuration;
8
+import org.springframework.scheduling.annotation.EnableScheduling;
9
+import org.springframework.scheduling.annotation.Scheduled;
10
+
11
+// ScheduledTasks.java
12
+@Configuration
13
+@EnableScheduling
14
+public class ScheduledTasks {
15
+
16
+    @Autowired
17
+    private IAdService adService;
18
+
19
+    @Autowired
20
+    private IVoteService voteService;
21
+
22
+    @Autowired
23
+    private IFinanceService financeService;
24
+
25
+    /**
26
+     * 每5分钟重试失败的广告同步
27
+     */
28
+    @Scheduled(fixedRate = 300000)
29
+    public void retryAdSync() {
30
+        adService.retryFailedSyncs();
31
+    }
32
+
33
+    /**
34
+     * 每10分钟重试失败的PHP通知
35
+     */
36
+    @Scheduled(cron = "0 */10 * * * ?")
37
+    public void retryVoteNotify() {
38
+        voteService.retryFailedNotifications();
39
+    }
40
+
41
+    /**
42
+     * 每天凌晨2点同步分账数据
43
+     */
44
+    @Scheduled(cron = "0 0 2 * * ?")
45
+    public void syncRoyaltyData() {
46
+        financeService.syncRoyaltyData();
47
+    }
48
+
49
+    /**
50
+     * 每小时刷新投票计数器
51
+     */
52
+    @Scheduled(cron = "0 0 * * * ?")
53
+    public void resetVoteCounters() {
54
+        // 实际应基于每日重置
55
+        log.info("Resetting vote counters...");
56
+        // 计数器重置逻辑
57
+    }
58
+}

+ 4
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/config/ThreadPoolConfig.java 查看文件

@@ -0,0 +1,4 @@
1
+package com.ruoyi.novel.config;
2
+
3
+public class ThreadPoolConfig {
4
+}

+ 25
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/controller/ChapterController.java 查看文件

@@ -0,0 +1,25 @@
1
+package com.ruoyi.novel.controller;
2
+
3
+import org.springframework.beans.factory.annotation.Autowired;
4
+import org.springframework.web.bind.annotation.RequestMapping;
5
+import org.springframework.web.bind.annotation.RestController;
6
+
7
+// ChapterController.java
8
+@RestController
9
+@RequestMapping("/chapter")
10
+public class ChapterController {
11
+
12
+    @Autowired
13
+    private ChapterService chapterService;
14
+
15
+    @GetMapping("/{novelId}")
16
+    public AjaxResult getChapters(@PathVariable Long novelId) {
17
+        List<NovelChapter> chapters = chapterService.selectChapterListByNovelId(novelId);
18
+        return AjaxResult.success(chapters);
19
+    }
20
+
21
+    @PostMapping("/save")
22
+    public AjaxResult saveChapter(@RequestBody ChapterDTO dto) {
23
+        return toAjax(chapterService.saveChapter(dto.getChapter(), dto.getContent()));
24
+    }
25
+}

+ 4
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/controller/NovelAdController.java 查看文件

@@ -0,0 +1,4 @@
1
+package com.ruoyi.novel.controller;
2
+//# 广告计数接口
3
+public class NovelAdController {
4
+}

+ 29
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/controller/NovelController.java 查看文件

@@ -0,0 +1,29 @@
1
+package com.ruoyi.novel.controller;
2
+
3
+import org.springframework.beans.factory.annotation.Autowired;
4
+import org.springframework.web.bind.annotation.GetMapping;
5
+import org.springframework.web.bind.annotation.RequestMapping;
6
+import org.springframework.web.bind.annotation.RestController;
7
+
8
+// NovelController.java
9
+@RestController
10
+@RequestMapping("/novel")
11
+public class NovelController {
12
+
13
+    @Autowired
14
+    private NovelService novelService;
15
+
16
+    @GetMapping("/list")
17
+    public TableDataInfo list(Novel novel) {
18
+        startPage();
19
+        List<Novel> list = novelService.selectNovelList(novel);
20
+        return getDataTable(list);
21
+    }
22
+
23
+    @PostMapping
24
+    public AjaxResult add(@RequestBody Novel novel) {
25
+        return toAjax(novelService.insertNovel(novel));
26
+    }
27
+
28
+    // 其他接口...
29
+}

+ 26
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/controller/NovelFinanceController.java 查看文件

@@ -0,0 +1,26 @@
1
+package com.ruoyi.novel.controller;
2
+
3
+import com.ruoyi.common.core.domain.AjaxResult;
4
+import org.springframework.beans.factory.annotation.Autowired;
5
+import org.springframework.web.bind.annotation.GetMapping;
6
+import org.springframework.web.bind.annotation.RequestMapping;
7
+import org.springframework.web.bind.annotation.RestController;
8
+
9
+//# 分账结算接口
10
+// NovelFinanceController.java
11
+@RestController
12
+@RequestMapping("/finance")
13
+public class NovelFinanceController {
14
+
15
+    @Autowired
16
+    private IFinanceService financeService;
17
+
18
+    @GetMapping("/royalty")
19
+    public AjaxResult getRoyalty(
20
+            @RequestParam Long authorId,
21
+            @RequestParam @DateTimeFormat(pattern="yyyy-MM") String month) {
22
+
23
+        RoyaltyReport report = financeService.calculateRoyalty(authorId, month);
24
+        return AjaxResult.success(report);
25
+    }
26
+}

+ 43
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/controller/NovelVoteController.java 查看文件

@@ -0,0 +1,43 @@
1
+package com.ruoyi.novel.controller;
2
+
3
+import org.springframework.beans.factory.annotation.Autowired;
4
+import org.springframework.web.bind.annotation.RequestMapping;
5
+import org.springframework.web.bind.annotation.RestController;
6
+
7
+//# 剧情投票接口
8
+// NovelVoteController.java
9
+@RestController
10
+@RequestMapping("/story/vote")
11
+public class NovelVoteController {
12
+
13
+    @Autowired
14
+    private IVoteService voteService;
15
+
16
+    // 使用Guava RateLimiter限制投票频率
17
+    private static final ConcurrentHashMap<Long, RateLimiter> userRateLimiters = new ConcurrentHashMap<>();
18
+    private static final int MAX_VOTES_PER_DAY = 3;
19
+
20
+    @PostMapping("/next")
21
+    public AjaxResult voteForStoryBranch(@RequestBody VoteRequest request) {
22
+        // 1. 频率限制检查
23
+        RateLimiter rateLimiter = userRateLimiters.computeIfAbsent(
24
+                request.getUserId(),
25
+                k -> RateLimiter.create(MAX_VOTES_PER_DAY)
26
+        );
27
+
28
+        if (!rateLimiter.tryAcquire()) {
29
+            return AjaxResult.error("Exceeded daily vote limit");
30
+        }
31
+
32
+        // 2. 调用DeepSeek API生成剧情
33
+        String generatedContent = voteService.generateStoryContent(
34
+                request.getStoryId(),
35
+                request.getOptionId()
36
+        );
37
+
38
+        // 3. 异步通知PHP系统
39
+        voteService.notifyPhpSystem(request, generatedContent);
40
+
41
+        return AjaxResult.success("Vote processed", generatedContent);
42
+    }
43
+}

+ 23
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/controller/ReadingHistoryController.java 查看文件

@@ -0,0 +1,23 @@
1
+package com.ruoyi.novel.controller;
2
+
3
+import org.springframework.web.bind.annotation.GetMapping;
4
+import org.springframework.web.bind.annotation.PostMapping;
5
+import org.springframework.web.bind.annotation.RequestMapping;
6
+import org.springframework.web.bind.annotation.RestController;
7
+
8
+@RestController
9
+@RequestMapping("/history")
10
+public class ReadingHistoryController {
11
+
12
+    @PostMapping("/record")
13
+    public AjaxResult recordReading(@RequestBody ReadingRecord record) {
14
+        // 记录用户阅读位置
15
+    }
16
+
17
+    @GetMapping("/last/{userId}/{novelId}")
18
+    public AjaxResult getLastPosition(
19
+            @PathVariable Long userId,
20
+            @PathVariable Long novelId) {
21
+        // 获取最后阅读位置
22
+    }
23
+}

+ 31
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/AdLog.java 查看文件

@@ -0,0 +1,31 @@
1
+package com.ruoyi.novel.domain;
2
+
3
+import lombok.Data;
4
+
5
+import java.util.Date;
6
+
7
+// AdLog.java
8
+@Data
9
+@TableName("novel_ad_log")
10
+public class AdLog {
11
+    @TableId(type = IdType.AUTO)
12
+    private Long id;
13
+
14
+    @NotNull(message = "用户ID不能为空")
15
+    private Long userId;
16
+
17
+    @NotNull(message = "章节ID不能为空")
18
+    private Long chapterId;
19
+
20
+    @NotBlank(message = "广告平台不能为空")
21
+    @Size(max = 50, message = "广告平台标识过长")
22
+    private String adPlatform;
23
+
24
+    @TableField(fill = FieldFill.INSERT)
25
+    private Date viewTime;
26
+
27
+    private String deviceInfo;
28
+
29
+    // 状态字段:0-未同步 1-已同步
30
+    private Integer syncStatus;
31
+}

+ 13
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/AdPlatformConfig.java 查看文件

@@ -0,0 +1,13 @@
1
+package com.ruoyi.novel.domain;
2
+
3
+@Data
4
+@TableName("novel_ad_config")
5
+public class AdPlatformConfig {
6
+    @TableId
7
+    private String platformCode;
8
+
9
+    private String platformName;
10
+    private String apiUrl;
11
+    private String authToken;
12
+    private String paramsTemplate;
13
+}

+ 21
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/Novel.java 查看文件

@@ -0,0 +1,21 @@
1
+package com.ruoyi.novel.domain;
2
+
3
+import lombok.Data;
4
+
5
+import java.util.Date;
6
+
7
+// Novel.java
8
+@Data
9
+public class Novel {
10
+    private Long id;
11
+    private String title;
12
+    private String author;
13
+    private String coverImg;
14
+    private Long categoryId;
15
+    private String status;
16
+    private String description;
17
+    private Long wordCount;
18
+    private Long readCount;
19
+    private Date createTime;
20
+    private Date updateTime;
21
+}

+ 11
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/NovelChapter.java 查看文件

@@ -0,0 +1,11 @@
1
+package com.ruoyi.novel.domain;
2
+// NovelChapter.java
3
+@Data
4
+public class NovelChapter {
5
+    private Long id;
6
+    private Long novelId;
7
+    private String chapterTitle;
8
+    private Integer chapterOrder;
9
+    private Date publishTime;
10
+    private String isVip;
11
+}

+ 7
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/NovelContent.java 查看文件

@@ -0,0 +1,7 @@
1
+package com.ruoyi.novel.domain;
2
+// NovelContent.java
3
+@Data
4
+public class NovelContent {
5
+    private Long chapterId;
6
+    private String content;
7
+}

+ 26
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/RoyaltyReport.java 查看文件

@@ -0,0 +1,26 @@
1
+package com.ruoyi.novel.domain;
2
+
3
+// RoyaltyReport.java
4
+@Data
5
+public class RoyaltyReport {
6
+    private Long authorId;
7
+    private String authorName;
8
+    private String month;
9
+    private BigDecimal totalReads;
10
+    private BigDecimal royaltyRate;
11
+    private BigDecimal handlingFeeRate;
12
+    private BigDecimal handlingFee;
13
+    private BigDecimal netAmount;
14
+    private String currency = "CNY";
15
+
16
+    // 计算逻辑
17
+    public void calculate(BigDecimal reads, BigDecimal rate, BigDecimal feeRate) {
18
+        this.totalReads = reads;
19
+        this.royaltyRate = rate;
20
+        this.handlingFeeRate = feeRate;
21
+
22
+        BigDecimal rawAmount = reads.multiply(rate);
23
+        this.handlingFee = rawAmount.multiply(feeRate);
24
+        this.netAmount = rawAmount.subtract(handlingFee);
25
+    }
26
+}

+ 20
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/RoyaltySummary.java 查看文件

@@ -0,0 +1,20 @@
1
+package com.ruoyi.novel.domain;
2
+
3
+import lombok.Data;
4
+
5
+import java.math.BigDecimal;
6
+
7
+// RoyaltySummary.java
8
+// RoyaltySummary.java
9
+@Data
10
+@TableName("novel_royalty_summary")
11
+public class RoyaltySummary {
12
+    @TableId
13
+    private Long authorId;
14
+
15
+    @TableId
16
+    private String month; // yyyy-MM格式
17
+
18
+    private BigDecimal totalReads;
19
+    private Date lastSyncTime;
20
+}

+ 17
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/StoryOption.java 查看文件

@@ -0,0 +1,17 @@
1
+package com.ruoyi.novel.domain;
2
+
3
+// StoryOption.java
4
+@Data
5
+@TableName("novel_story_option")
6
+public class StoryOption {
7
+    @TableId(type = IdType.AUTO)
8
+    private Long id;
9
+
10
+    @NotNull(message = "故事ID不能为空")
11
+    private Long storyId;
12
+
13
+    @NotBlank(message = "选项内容不能为空")
14
+    private String optionText;
15
+
16
+    private Integer voteCount;
17
+}

+ 26
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/VoteRecord.java 查看文件

@@ -0,0 +1,26 @@
1
+package com.ruoyi.novel.domain;
2
+
3
+import lombok.Data;
4
+
5
+import java.util.Date;
6
+
7
+// VoteRecord.java
8
+// VoteRecord.java
9
+@Data
10
+@TableName("novel_vote_log")
11
+public class VoteRecord {
12
+    @TableId(type = IdType.AUTO)
13
+    private Long id;
14
+
15
+    private Long userId;
16
+    private Long storyId;
17
+    private Long optionId;
18
+
19
+    @TableField(fill = FieldFill.INSERT)
20
+    private Date voteTime;
21
+
22
+    @Column(typeHandler = LongTextTypeHandler.class)
23
+    private String generatedContent;
24
+
25
+    private Integer notifyStatus; // 0-未通知 1-已通知
26
+}

+ 14
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/domain/VoteRequest.java 查看文件

@@ -0,0 +1,14 @@
1
+package com.ruoyi.novel.domain;
2
+
3
+// VoteRequest.java
4
+@Data
5
+public class VoteRequest {
6
+    @NotNull(message = "用户ID不能为空")
7
+    private Long userId;
8
+
9
+    @NotNull(message = "故事ID不能为空")
10
+    private Long storyId;
11
+
12
+    @NotNull(message = "选项ID不能为空")
13
+    private Long optionId;
14
+}

+ 12
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/mapper/AdLogMapper.java 查看文件

@@ -0,0 +1,12 @@
1
+package com.ruoyi.novel.mapper;
2
+
3
+// AdLogMapper.java
4
+@Mapper
5
+public interface AdLogMapper extends BaseMapper<AdLog> {
6
+
7
+    @Select("SELECT COUNT(*) FROM novel_ad_log WHERE user_id = #{userId} AND chapter_id = #{chapterId}")
8
+    int countByUserAndChapter(@Param("userId") Long userId, @Param("chapterId") Long chapterId);
9
+
10
+    @Select("SELECT * FROM novel_ad_log WHERE sync_status = 0 ORDER BY view_time ASC LIMIT 100")
11
+    List<AdLog> selectUnsyncedLogs();
12
+}

+ 12
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/mapper/NovelChapterMapper.java 查看文件

@@ -0,0 +1,12 @@
1
+package com.ruoyi.novel.mapper;
2
+
3
+import org.apache.ibatis.annotations.Mapper;
4
+
5
+// NovelChapterMapper.java
6
+@Mapper
7
+public interface NovelChapterMapper {
8
+    List<NovelChapter> selectChapterListByNovelId(Long novelId);
9
+    NovelChapter selectChapterById(Long chapterId);
10
+    int insertChapter(NovelChapter chapter);
11
+    int updateChapter(NovelChapter chapter);
12
+}

+ 10
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/mapper/NovelMapper.java 查看文件

@@ -0,0 +1,10 @@
1
+package com.ruoyi.novel.mapper;
2
+// NovelMapper.java
3
+public interface NovelMapper {
4
+    List<Novel> selectNovelList(Novel novel);
5
+    Novel selectNovelById(Long id);
6
+    int insertNovel(Novel novel);
7
+    int updateNovel(Novel novel);
8
+    int deleteNovelByIds(Long[] ids);
9
+}
10
+

+ 18
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/mapper/RoyaltySummaryMapper.java 查看文件

@@ -0,0 +1,18 @@
1
+package com.ruoyi.novel.mapper;
2
+
3
+// RoyaltySummaryMapper.java
4
+@Mapper
5
+public interface RoyaltySummaryMapper extends BaseMapper<RoyaltySummary> {
6
+
7
+    @Select("SELECT * FROM novel_royalty_summary " +
8
+            "WHERE author_id = #{authorId} AND month = #{month}")
9
+    RoyaltySummary selectByAuthorAndMonth(
10
+            @Param("authorId") Long authorId,
11
+            @Param("month") String month
12
+    );
13
+
14
+    @Update("INSERT INTO novel_royalty_summary (author_id, month, total_reads, last_sync_time) " +
15
+            "VALUES (#{authorId}, #{month}, #{totalReads}, NOW()) " +
16
+            "ON DUPLICATE KEY UPDATE total_reads = #{totalReads}, last_sync_time = NOW()")
17
+    int upsertSummary(RoyaltySummary summary);
18
+}

+ 16
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/mapper/VoteRecordMapper.java 查看文件

@@ -0,0 +1,16 @@
1
+package com.ruoyi.novel.mapper;
2
+
3
+// VoteRecordMapper.java
4
+@Mapper
5
+public interface VoteRecordMapper extends BaseMapper<VoteRecord> {
6
+
7
+    @Select("SELECT COUNT(*) FROM novel_vote_log " +
8
+            "WHERE user_id = #{userId} AND story_id = #{storyId}")
9
+    int countUserVotesForStory(@Param("userId") Long userId, @Param("storyId") Long storyId);
10
+
11
+    @Update("UPDATE novel_vote_log SET notify_status = #{status} WHERE id = #{id}")
12
+    int updateNotifyStatus(@Param("id") Long id, @Param("status") Integer status);
13
+
14
+    @Select("SELECT * FROM novel_vote_log WHERE notify_status = 0 ORDER BY vote_time ASC LIMIT 50")
15
+    List<VoteRecord> selectUnnotifiedRecords();
16
+}

+ 8
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/IAdService.java 查看文件

@@ -0,0 +1,8 @@
1
+package com.ruoyi.novel.service;
2
+
3
+public interface IAdService {
4
+    void logAdView(AdLog adLog);
5
+    void syncWithAdPlatform(AdLog adLog);
6
+    void retryFailedSyncs();
7
+    Map<String, AdPlatformConfig> loadAdConfigs();
8
+}

+ 9
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/IFinanceService.java 查看文件

@@ -0,0 +1,9 @@
1
+package com.ruoyi.novel.service;
2
+
3
+// IFinanceService.java
4
+public interface IFinanceService {
5
+    RoyaltyReport calculateRoyalty(Long authorId, String month);
6
+    void syncRoyaltyData();
7
+    BigDecimal getRoyaltyRate(Long authorId);
8
+    BigDecimal getHandlingFeeRate();
9
+}

+ 9
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/IVoteService.java 查看文件

@@ -0,0 +1,9 @@
1
+package com.ruoyi.novel.service;
2
+
3
+// IVoteService.java
4
+public interface IVoteService {
5
+    String processVote(VoteRequest request);
6
+    void generateStoryContent(VoteRecord record);
7
+    void notifyPhpSystem(VoteRecord record);
8
+    void retryFailedNotifications();
9
+}

+ 4
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/NovelSearchService.java 查看文件

@@ -0,0 +1,4 @@
1
+package com.ruoyi.novel.service;
2
+
3
+public class NovelSearchService {
4
+}

+ 92
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/impl/AdServiceImpl.java 查看文件

@@ -0,0 +1,92 @@
1
+package com.ruoyi.novel.service.impl;
2
+
3
+import com.ruoyi.novel.service.IAdService;
4
+
5
+@Service
6
+public class AdServiceImpl implements IAdService {
7
+
8
+    @Autowired
9
+    private AdLogMapper adLogMapper;
10
+
11
+    @Autowired
12
+    private AdPlatformClient adPlatformClient;
13
+
14
+    private Map<String, AdPlatformConfig> adConfigCache;
15
+    private final Object lock = new Object();
16
+    @Override
17
+    @Transactional
18
+    public void logAdView(AdLog adLog) {
19
+        // 防止重复记录
20
+        int count = adLogMapper.countByUserAndChapter(adLog.getUserId(), adLog.getChapterId());
21
+        if (count > 0) {
22
+            log.warn("Duplicate ad log: user={}, chapter={}", adLog.getUserId(), adLog.getChapterId());
23
+            return;
24
+        }
25
+
26
+        adLog.setSyncStatus(0); // 未同步状态
27
+        adLogMapper.insert(adLog);
28
+    }
29
+
30
+    @Override
31
+    @Async("adTaskExecutor")
32
+    public void syncWithAdPlatform(AdLog adLog) {
33
+        try {
34
+            adPlatformClient.syncAdPlatform(adLog);
35
+            adLog.setSyncStatus(1); // 同步成功
36
+        } catch (Exception e) {
37
+            log.error("Ad sync failed: {}", e.getMessage());
38
+            adLog.setSyncStatus(2); // 同步失败
39
+        }
40
+        adLogMapper.updateById(adLog);
41
+    }
42
+
43
+    @Override
44
+    @Scheduled(fixedRate = 300000) // 每5分钟执行一次
45
+    public void retryFailedSyncs() {
46
+        List<AdLog> failedLogs = adLogMapper.selectUnsyncedLogs();
47
+        failedLogs.forEach(log -> {
48
+            log.info("Retrying ad sync for log: {}", log.getId());
49
+            this.syncWithAdPlatform(log);
50
+        });
51
+    }
52
+
53
+    @Override
54
+    public Map<String, AdPlatformConfig> loadAdConfigs() {
55
+        if (adConfigCache == null) {
56
+            synchronized (lock) {
57
+                if (adConfigCache == null) {
58
+                    // 实际应从数据库加载
59
+                    adConfigCache = new HashMap<>();
60
+                    adConfigCache.put("tencent", new AdPlatformConfig(
61
+                            "tencent", "腾讯广告",
62
+                            "https://ad.tencent.com/track",
63
+                            "token_123",
64
+                            "{\"user_id\":\"${userId}\",\"chapter_id\":\"${chapterId}\"}"
65
+                    ));
66
+                    // 其他平台配置...
67
+                }
68
+            }
69
+        }
70
+        return adConfigCache;
71
+    }
72
+//    @Override
73
+//    @Transactional
74
+//    public void logAdView(AdLog adLog) {
75
+//        adLogMapper.insertAdLog(adLog);
76
+//    }
77
+
78
+    @Override
79
+    public void syncWithAdPlatform(AdLog adLog) {
80
+        // 根据平台选择不同实现
81
+        switch (adLog.getAdPlatform()) {
82
+            case "tencent":
83
+                adPlatformClient.syncTencent(adLog);
84
+                break;
85
+            case "pangle":
86
+                adPlatformClient.syncPangle(adLog);
87
+                break;
88
+            default:
89
+                log.warn("Unknown ad platform: {}", adLog.getAdPlatform());
90
+        }
91
+    }
92
+}

+ 22
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/impl/ChapterServiceImpl.java 查看文件

@@ -0,0 +1,22 @@
1
+package com.ruoyi.novel.service.impl;
2
+
3
+// ChapterServiceImpl.java
4
+@Service
5
+public class ChapterServiceImpl implements ChapterService {
6
+
7
+    @Autowired
8
+    private NovelChapterMapper chapterMapper;
9
+
10
+    @Autowired
11
+    private NovelContentMapper contentMapper;
12
+
13
+    @Override
14
+    @Transactional
15
+    public int saveChapter(NovelChapter chapter, String content) {
16
+        chapterMapper.insertChapter(chapter);
17
+        NovelContent novelContent = new NovelContent();
18
+        novelContent.setChapterId(chapter.getId());
19
+        novelContent.setContent(content);
20
+        return contentMapper.insertContent(novelContent);
21
+    }
22
+}

+ 110
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/impl/FinanceServiceImpl.java 查看文件

@@ -0,0 +1,110 @@
1
+package com.ruoyi.novel.service.impl;
2
+
3
+import com.ruoyi.novel.domain.RoyaltyReport;
4
+import com.ruoyi.novel.domain.RoyaltySummary;
5
+import com.ruoyi.novel.mapper.RoyaltySummaryMapper;
6
+import com.ruoyi.novel.service.IFinanceService;
7
+import lombok.Value;
8
+import org.springframework.beans.factory.annotation.Autowired;
9
+import org.springframework.scheduling.annotation.Scheduled;
10
+import org.springframework.stereotype.Service;
11
+
12
+import java.math.BigDecimal;
13
+import java.util.ArrayList;
14
+import java.util.List;
15
+
16
+// FinanceServiceImpl.java
17
+@Service
18
+public class FinanceServiceImpl implements IFinanceService {
19
+
20
+    @Autowired
21
+    private RoyaltySummaryMapper royaltySummaryMapper;
22
+    @Value("${royalty.base.rate:0.15}")
23
+    private BigDecimal baseRoyaltyRate;
24
+
25
+    @Value("${handling.fee.rate:0.05}")
26
+    private BigDecimal handlingFeeRate;
27
+    @Value("${royalty.rate}")
28
+    private BigDecimal royaltyRate;
29
+
30
+    @Value("${handling.fee.rate}")
31
+    private BigDecimal handlingFeeRate;
32
+
33
+    @Override
34
+    public RoyaltyReport calculateRoyalty(Long authorId, String month) {
35
+        // 1. 从PHP主库获取基础数据(假设已通过定时任务同步)
36
+        RoyaltySummary summary = royaltySummaryMapper.selectByAuthorAndMonth(authorId, month);
37
+
38
+//        if (summary == null) {
39
+//            throw new ServiceException("No data found for author: " + authorId + " in " + month);
40
+//        }
41
+        if (summary == null) {
42
+            throw new ServiceException("未找到分账数据: author=" + authorId + ", month=" + month);
43
+        }
44
+        // 2. 计算实际分成比例(可根据作者等级调整)
45
+        BigDecimal actualRate = getRoyaltyRate(authorId);
46
+        // 3. 构建报表
47
+        RoyaltyReport report = new RoyaltyReport();
48
+        report.setAuthorId(authorId);
49
+        report.setMonth(month);
50
+        report.calculate(summary.getTotalReads(), actualRate, handlingFeeRate);
51
+
52
+        return report;
53
+
54
+        // 2. 计算分账金额
55
+//        BigDecimal rawAmount = summary.getTotalReads().multiply(royaltyRate);
56
+//        BigDecimal handlingFee = rawAmount.multiply(handlingFeeRate);
57
+//        BigDecimal netAmount = rawAmount.subtract(handlingFee);
58
+//
59
+//        // 3. 构建报表
60
+//        return new RoyaltyReport(
61
+//                authorId,
62
+//                month,
63
+//                summary.getTotalReads(),
64
+//                royaltyRate,
65
+//                handlingFeeRate,
66
+//                handlingFee,
67
+//                netAmount
68
+//        );
69
+    }
70
+
71
+    @Override
72
+    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
73
+    public void syncRoyaltyData() {
74
+        log.info("开始同步分账数据...");
75
+        try {
76
+            // 1. 从PHP主库获取数据
77
+            List<RoyaltySummary> summaries = fetchFromPhpSystem();
78
+
79
+            // 2. 更新到本地数据库
80
+            summaries.forEach(summary -> {
81
+                royaltySummaryMapper.upsertSummary(summary);
82
+            });
83
+
84
+            log.info("同步完成: {}条记录", summaries.size());
85
+        } catch (Exception e) {
86
+            log.error("分账数据同步失败: {}", e.getMessage());
87
+        }
88
+    }
89
+
90
+    private List<RoyaltySummary> fetchFromPhpSystem() {
91
+        // 实现从PHP系统获取数据的逻辑
92
+        // 返回模拟数据
93
+        List<RoyaltySummary> list = new ArrayList<>();
94
+        list.add(new RoyaltySummary(1001L, "2023-10", new BigDecimal("15000.00")));
95
+        list.add(new RoyaltySummary(1002L, "2023-10", new BigDecimal("8500.00")));
96
+        return list;
97
+    }
98
+
99
+    @Override
100
+    public BigDecimal getRoyaltyRate(Long authorId) {
101
+        // 实际应根据作者等级计算
102
+        // 这里返回基础比例
103
+        return baseRoyaltyRate;
104
+    }
105
+
106
+    @Override
107
+    public BigDecimal getHandlingFeeRate() {
108
+        return handlingFeeRate;
109
+    }
110
+}

+ 16
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/impl/NovelSearchServiceImpl.java 查看文件

@@ -0,0 +1,16 @@
1
+package com.ruoyi.novel.service.impl;
2
+
3
+// NovelSearchService.java
4
+@Service
5
+public class NovelSearchServiceImpl implements NovelSearchService {
6
+
7
+    @Autowired
8
+    private ElasticsearchRestTemplate elasticTemplate;
9
+
10
+    public List<Novel> searchNovels(String keyword) {
11
+        NativeSearchQuery query = new NativeSearchQueryBuilder()
12
+                .withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "author", "description"))
13
+                .build();
14
+        return elasticTemplate.search(query, Novel.class).getContent();
15
+    }
16
+}

+ 25
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/impl/NovelServiceImpl.java 查看文件

@@ -0,0 +1,25 @@
1
+package com.ruoyi.novel.service.impl;
2
+
3
+import org.springframework.stereotype.Service;
4
+
5
+// NovelServiceImpl.java
6
+@Service
7
+public class NovelServiceImpl implements NovelService {
8
+
9
+    @Autowired
10
+    private NovelMapper novelMapper;
11
+
12
+    @Override
13
+    public List<Novel> selectNovelList(Novel novel) {
14
+        return novelMapper.selectNovelList(novel);
15
+    }
16
+
17
+    @Override
18
+    @Transactional
19
+    public int insertNovel(Novel novel) {
20
+        novel.setCreateTime(new Date());
21
+        return novelMapper.insertNovel(novel);
22
+    }
23
+
24
+    // 其他CRUD方法...
25
+}

+ 151
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/service/impl/VoteServiceImpl.java 查看文件

@@ -0,0 +1,151 @@
1
+package com.ruoyi.novel.service.impl;
2
+
3
+// VoteServiceImpl.java
4
+@Service
5
+public class VoteServiceImpl implements IVoteService {
6
+
7
+    @Autowired
8
+    private VoteRecordMapper voteRecordMapper;
9
+    @Autowired
10
+    private StoryOptionMapper storyOptionMapper;
11
+    @Value("${deepseek.api.key}")
12
+    private String deepSeekApiKey;
13
+
14
+    @Value("${php.notify.url}")
15
+    private String phpNotifyUrl;
16
+    // 用户投票限流器 <userId, 计数器>
17
+    private final Map<Long, AtomicInteger> userVoteCounters = new ConcurrentHashMap<>();
18
+    private static final int DAILY_VOTE_LIMIT = 3;
19
+
20
+    @Override
21
+    public String processVote(VoteRequest request) {
22
+        // 1. 检查当日投票次数
23
+        AtomicInteger counter = userVoteCounters.computeIfAbsent(
24
+                request.getUserId(),
25
+                k -> new AtomicInteger(0)
26
+        );
27
+
28
+        if (counter.get() >= DAILY_VOTE_LIMIT) {
29
+            throw new ServiceException("今日投票次数已达上限");
30
+        }
31
+
32
+        // 2. 记录投票
33
+        VoteRecord record = new VoteRecord();
34
+        record.setUserId(request.getUserId());
35
+        record.setStoryId(request.getStoryId());
36
+        record.setOptionId(request.getOptionId());
37
+        record.setNotifyStatus(0); // 未通知状态
38
+        voteRecordMapper.insert(record);
39
+
40
+        // 3. 更新选项计数
41
+        storyOptionMapper.incrementOptionVote(request.getOptionId());
42
+
43
+        // 4. 异步生成内容
44
+        this.generateStoryContent(record);
45
+
46
+        counter.incrementAndGet();
47
+        return "投票成功,剧情生成中...";
48
+    }
49
+    @Override
50
+    @Async("voteTaskExecutor")
51
+    public void generateStoryContent(VoteRecord record) {
52
+        try {
53
+            String content = callDeepSeekApi(record);
54
+            record.setGeneratedContent(content);
55
+            voteRecordMapper.updateById(record);
56
+
57
+            // 通知PHP系统
58
+            this.notifyPhpSystem(record);
59
+        } catch (Exception e) {
60
+            log.error("AI生成失败: {}", e.getMessage());
61
+        }
62
+    }
63
+    private String callDeepSeekApi(VoteRecord record) {
64
+        // 实现DeepSeek API调用
65
+        // 返回生成的剧情内容
66
+        return "生成的故事内容...";
67
+    }
68
+
69
+//    @Async
70
+//    @Override
71
+//    public void generateStoryContent(Long storyId, Long optionId) {
72
+//        // 调用DeepSeek API
73
+//        DeepSeekRequest request = new DeepSeekRequest(storyId, optionId);
74
+//        String apiUrl = "https://api.deepseek.com/v1/story/generate";
75
+//
76
+//        RestTemplate restTemplate = new RestTemplate();
77
+//        HttpHeaders headers = new HttpHeaders();
78
+//        headers.setBearerAuth(deepSeekApiKey);
79
+//
80
+//        HttpEntity<DeepSeekRequest> entity = new HttpEntity<>(request, headers);
81
+//        ResponseEntity<String> response = restTemplate.exchange(
82
+//                apiUrl, HttpMethod.POST, entity, String.class);
83
+//
84
+//        if (response.getStatusCode() == HttpStatus.OK) {
85
+//            return parseGeneratedContent(response.getBody());
86
+//        }
87
+//        throw new ServiceException("Failed to generate story content");
88
+//    }
89
+
90
+    @Async
91
+    @Override
92
+    public void notifyPhpSystem(VoteRequest request, String content) {
93
+        // 组装通知数据
94
+        Map<String, Object> payload = new HashMap<>();
95
+        payload.put("userId", request.getUserId());
96
+        payload.put("storyId", request.getStoryId());
97
+        payload.put("optionId", request.getOptionId());
98
+        payload.put("content", content);
99
+
100
+        // 发送异步通知
101
+        RestTemplate restTemplate = new RestTemplate();
102
+        restTemplate.postForObject(phpNotifyUrl, payload, String.class);
103
+
104
+        // 记录投票日志
105
+        VoteRecord record = new VoteRecord();
106
+        record.setUserId(request.getUserId());
107
+        record.setStoryId(request.getStoryId());
108
+        record.setOptionId(request.getOptionId());
109
+        record.setVoteTime(new Date());
110
+        record.setGeneratedContent(content);
111
+        voteRecordMapper.insertVoteRecord(record);
112
+    }
113
+
114
+    @Override
115
+    @Async("voteTaskExecutor")
116
+    public void notifyPhpSystem(VoteRecord record) {
117
+        try {
118
+            // 构建通知数据
119
+            Map<String, Object> payload = new HashMap<>();
120
+            payload.put("userId", record.getUserId());
121
+            payload.put("storyId", record.getStoryId());
122
+            payload.put("optionId", record.getOptionId());
123
+            payload.put("content", record.getGeneratedContent());
124
+
125
+            // 发送HTTP请求
126
+            RestTemplate restTemplate = new RestTemplate();
127
+            ResponseEntity<String> response = restTemplate.postForEntity(
128
+                    phpNotifyUrl, payload, String.class);
129
+
130
+            if (response.getStatusCode() == HttpStatus.OK) {
131
+                record.setNotifyStatus(1); // 通知成功
132
+            } else {
133
+                record.setNotifyStatus(2); // 通知失败
134
+            }
135
+        } catch (Exception e) {
136
+            log.error("通知PHP系统失败: {}", e.getMessage());
137
+            record.setNotifyStatus(2);
138
+        }
139
+        voteRecordMapper.updateById(record);
140
+    }
141
+
142
+    @Override
143
+    @Scheduled(cron = "0 */10 * * * ?") // 每10分钟执行一次
144
+    public void retryFailedNotifications() {
145
+        List<VoteRecord> failedRecords = voteRecordMapper.selectUnnotifiedRecords();
146
+        failedRecords.forEach(record -> {
147
+            log.info("重试通知PHP系统: voteId={}", record.getId());
148
+            this.notifyPhpSystem(record);
149
+        });
150
+    }
151
+}

+ 80
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/utils/AdPlatformClient.java 查看文件

@@ -0,0 +1,80 @@
1
+package com.ruoyi.novel.utils;
2
+
3
+import com.ruoyi.novel.service.IAdService;
4
+import org.springframework.beans.factory.annotation.Autowired;
5
+import org.springframework.stereotype.Component;
6
+import org.springframework.web.client.RestTemplate;
7
+
8
+// AdPlatformClient.java
9
+@Component
10
+public class AdPlatformClient {
11
+    @Autowired
12
+    private IAdService adService;
13
+
14
+    @Autowired
15
+    private RestTemplate restTemplate;
16
+    @Value("${ad.tencent.url}")
17
+    private String tencentUrl;
18
+
19
+    @Value("${ad.pangle.url}")
20
+    private String pangleUrl;
21
+    public void syncAdPlatform(AdLog adLog) {
22
+        Map<String, AdPlatformConfig> configs = adService.loadAdConfigs();
23
+        AdPlatformConfig config = configs.get(adLog.getAdPlatform());
24
+
25
+        if (config == null) {
26
+            throw new ServiceException("广告平台配置不存在: " + adLog.getAdPlatform());
27
+        }
28
+
29
+        // 构建请求参数
30
+        String payload = buildPayload(adLog, config.getParamsTemplate());
31
+
32
+        // 设置请求头
33
+        HttpHeaders headers = new HttpHeaders();
34
+        headers.setContentType(MediaType.APPLICATION_JSON);
35
+        headers.setBearerAuth(config.getAuthToken());
36
+
37
+        // 发送请求
38
+        HttpEntity<String> entity = new HttpEntity<>(payload, headers);
39
+        ResponseEntity<String> response = restTemplate.exchange(
40
+                config.getApiUrl(), HttpMethod.POST, entity, String.class);
41
+
42
+        // 处理响应
43
+        if (!response.getStatusCode().is2xxSuccessful()) {
44
+            throw new ServiceException("广告平台同步失败: " + response.getBody());
45
+        }
46
+
47
+        log.info("广告同步成功: logId={}, platform={}", adLog.getId(), adLog.getAdPlatform());
48
+    }
49
+    private String buildPayload(AdLog adLog, String template) {
50
+        // 使用占位符替换实际值
51
+        return template
52
+                .replace("${userId}", adLog.getUserId().toString())
53
+                .replace("${chapterId}", adLog.getChapterId().toString())
54
+                .replace("${timestamp}", String.valueOf(System.currentTimeMillis()));
55
+    }
56
+    public void syncTencent(AdLog adLog) {
57
+        Map<String, String> params = new HashMap<>();
58
+        params.put("user_id", adLog.getUserId().toString());
59
+        params.put("chapter_id", adLog.getChapterId().toString());
60
+        params.put("event_time", String.valueOf(adLog.getViewTime().getTime()));
61
+
62
+        RestTemplate restTemplate = new RestTemplate();
63
+        restTemplate.postForObject(tencentUrl, params, String.class);
64
+    }
65
+
66
+    public void syncPangle(AdLog adLog) {
67
+        // 穿山甲平台需要不同的参数格式
68
+        JSONObject payload = new JSONObject();
69
+        payload.put("user", adLog.getUserId());
70
+        payload.put("content_id", adLog.getChapterId());
71
+        payload.put("timestamp", System.currentTimeMillis());
72
+
73
+        RestTemplate restTemplate = new RestTemplate();
74
+        HttpHeaders headers = new HttpHeaders();
75
+        headers.setContentType(MediaType.APPLICATION_JSON);
76
+
77
+        HttpEntity<String> entity = new HttpEntity<>(payload.toString(), headers);
78
+        restTemplate.postForObject(pangleUrl, entity, String.class);
79
+    }
80
+}

+ 47
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/utils/JwtUtil.java 查看文件

@@ -0,0 +1,47 @@
1
+package com.ruoyi.novel.utils;
2
+
3
+import io.jsonwebtoken.Claims;
4
+import io.jsonwebtoken.Jwts;
5
+import io.jsonwebtoken.SignatureAlgorithm;
6
+
7
+import java.util.Date;
8
+
9
+// JwtUtil.java
10
+public class JwtUtil {
11
+    private static final String SECRET = "your_jwt_secret_key";
12
+    private static final long EXPIRE = 86400; // 24小时
13
+    public static String generateToken(Long userId) {
14
+        Date now = new Date();
15
+        Date expire = new Date(now.getTime() + EXPIRE * 1000);
16
+
17
+        return Jwts.builder()
18
+                .setHeaderParam("typ", "JWT")
19
+                .setSubject(userId.toString())
20
+                .setIssuedAt(now)
21
+                .setExpiration(expire)
22
+                .signWith(SignatureAlgorithm.HS512, SECRET)
23
+                .compact();
24
+    }
25
+
26
+    public static boolean verifyToken(String token, Long userId) {
27
+        try {
28
+            Claims claims = Jwts.parser()
29
+                    .setSigningKey(SECRET)
30
+                    .parseClaimsJws(token)
31
+                    .getBody();
32
+
33
+            String subject = claims.getSubject();
34
+            return userId.toString().equals(subject);
35
+        } catch (Exception e) {
36
+            return false;
37
+        }
38
+    }
39
+
40
+    public static Long getUserIdFromToken(String token) {
41
+        Claims claims = Jwts.parser()
42
+                .setSigningKey(SECRET)
43
+                .parseClaimsJws(token)
44
+                .getBody();
45
+        return Long.parseLong(claims.getSubject());
46
+    }
47
+}

+ 15
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/utils/NovelUtils.java 查看文件

@@ -0,0 +1,15 @@
1
+package com.ruoyi.novel.utils;
2
+
3
+// NovelUtils.java
4
+public class NovelUtils {
5
+
6
+    // 敏感词过滤
7
+    public static String filterSensitiveWords(String content) {
8
+        // 实现敏感词过滤逻辑
9
+    }
10
+
11
+    // 自动生成章节序号
12
+    public static Integer generateChapterOrder(Long novelId) {
13
+        // 查询最新章节序号+1
14
+    }
15
+}

+ 15
- 0
RuoYi-Vue/ruoyi-novel/src/main/java/com/ruoyi/novel/vip/VipAccessAspect.java 查看文件

@@ -0,0 +1,15 @@
1
+package com.ruoyi.novel.vip;
2
+
3
+// VipAccessAspect.java
4
+@Aspect
5
+@Component
6
+public class VipAccessAspect {
7
+
8
+    @Before("@annotation(vipAccess)")
9
+    public void checkVipAccess(JoinPoint joinPoint, VipAccess vipAccess) {
10
+        // 校验用户VIP状态
11
+        if(!userService.isVip(SecurityUtils.getUserId())) {
12
+            throw new ServiceException("请开通VIP后阅读此章节");
13
+        }
14
+    }
15
+}

+ 26
- 0
RuoYi-Vue/ruoyi-novel/src/main/resources/application.yml 查看文件

@@ -0,0 +1,26 @@
1
+# 广告平台配置
2
+ad:
3
+  tencent:
4
+    url: https://ad.tencent.com/track
5
+  pangle:
6
+    url: https://ad.pangle.com/api/v1/event
7
+
8
+# DeepSeek API配置
9
+deepseek:
10
+  api:
11
+    key: your_deepseek_api_key
12
+    url: https://api.deepseek.com/v1/story/generate
13
+
14
+# PHP系统配置
15
+php:
16
+  notify:
17
+    url: http://php-system.com/api/vote/notify
18
+  data:
19
+    url: http://php-system.com/api/data
20
+
21
+# 分账规则
22
+royalty:
23
+  rate: 0.15   # 15%分成比例
24
+handling:
25
+  fee:
26
+    rate: 0.05  # 5%提现手续费

+ 27
- 0
RuoYi-Vue/ruoyi-novel/src/main/resources/mapper/novel/AdLogMapper.xml 查看文件

@@ -0,0 +1,27 @@
1
+<!-- AdLogMapper.xml -->
2
+<mapper namespace="com.ruoyi.novel.mapper.AdLogMapper">
3
+
4
+    <resultMap id="AdLogResult" type="AdLog">
5
+        <id property="id" column="id" />
6
+        <result property="userId" column="user_id" />
7
+        <result property="chapterId" column="chapter_id" />
8
+        <result property="adPlatform" column="ad_platform" />
9
+        <result property="viewTime" column="view_time" />
10
+        <result property="deviceInfo" column="device_info" />
11
+        <result property="syncStatus" column="sync_status" />
12
+    </resultMap>
13
+
14
+    <sql id="selectAdLogVo">
15
+        SELECT id, user_id, chapter_id, ad_platform, view_time, device_info, sync_status
16
+        FROM novel_ad_log
17
+    </sql>
18
+
19
+    <update id="updateSyncStatus">
20
+        UPDATE novel_ad_log
21
+        SET sync_status = #{status}
22
+        WHERE id IN
23
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
24
+            #{id}
25
+        </foreach>
26
+    </update>
27
+</mapper>

+ 17
- 0
RuoYi-Vue/ruoyi-novel/src/main/resources/mapper/novel/RoyaltySummaryMapper.xml 查看文件

@@ -0,0 +1,17 @@
1
+<!-- RoyaltySummaryMapper.xml -->
2
+<mapper namespace="com.ruoyi.novel.mapper.RoyaltySummaryMapper">
3
+
4
+    <resultMap id="RoyaltySummaryResult" type="RoyaltySummary">
5
+        <id property="authorId" column="author_id" />
6
+        <id property="month" column="month" />
7
+        <result property="totalReads" column="total_reads" />
8
+        <result property="lastSyncTime" column="last_sync_time" />
9
+    </resultMap>
10
+
11
+    <select id="selectMonthlySummary" resultMap="RoyaltySummaryResult">
12
+        SELECT author_id, month, SUM(total_reads) AS total_reads
13
+        FROM novel_royalty_detail
14
+        WHERE month = #{month}
15
+        GROUP BY author_id
16
+    </select>
17
+</mapper>

+ 20
- 0
RuoYi-Vue/ruoyi-novel/src/main/resources/mapper/novel/VoteRecordMapper.xml 查看文件

@@ -0,0 +1,20 @@
1
+<!-- VoteRecordMapper.xml -->
2
+<mapper namespace="com.ruoyi.novel.mapper.VoteRecordMapper">
3
+
4
+    <resultMap id="VoteRecordResult" type="VoteRecord">
5
+        <id property="id" column="id" />
6
+        <result property="userId" column="user_id" />
7
+        <result property="storyId" column="story_id" />
8
+        <result property="optionId" column="option_id" />
9
+        <result property="voteTime" column="vote_time" />
10
+        <result property="generatedContent" column="generated_content"
11
+                typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
12
+        <result property="notifyStatus" column="notify_status" />
13
+    </resultMap>
14
+
15
+    <update id="incrementOptionVote">
16
+        UPDATE novel_story_option
17
+        SET vote_count = vote_count + 1
18
+        WHERE id = #{optionId}
19
+    </update>
20
+</mapper>

正在加载...
取消
保存