Просмотр исходного кода

feat(zhaozhaonews): 新增自媒体和管理端服务 - wemedia(自媒体登录/频道/素材/文章管理/统计) - admin(管理端登录/审核/频道管理/用户认证)

WanJL 3 недель назад
Родитель
Сommit
9b603d8eed
46 измененных файлов с 1967 добавлено и 0 удалено
  1. 20 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/pom.xml
  2. 23 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/AdminApplication.java
  3. 18 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/config/MybatisPlusConfig.java
  4. 33 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/controller/AuthController.java
  5. 78 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/controller/ChannelController.java
  6. 21 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/controller/LoginController.java
  7. 123 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/controller/NewsController.java
  8. 7 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/AdUserMapper.java
  9. 7 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/ApArticleConfigMapper.java
  10. 7 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/ApArticleContentMapper.java
  11. 7 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/ApArticleMapper.java
  12. 7 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/ApUserRealnameMapper.java
  13. 7 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/WmChannelMapper.java
  14. 7 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/WmNewsMapper.java
  15. 8 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/service/AdUserService.java
  16. 10 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/service/ApUserRealnameService.java
  17. 44 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/service/impl/AdUserServiceImpl.java
  18. 55 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/service/impl/ApUserRealnameServiceImpl.java
  19. 12 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/resources/bootstrap.yml
  20. 58 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/resources/logback-spring.xml
  21. 33 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/pom.xml
  22. 47 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/WeMediaApplication.java
  23. 44 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/config/GlobalExceptionHandler.java
  24. 39 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/config/WebMvcConfig.java
  25. 23 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/controller/LoginController.java
  26. 74 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/controller/StatisticsController.java
  27. 34 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/controller/WmChannelController.java
  28. 52 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/controller/WmMaterialController.java
  29. 56 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/controller/WmNewsController.java
  30. 78 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/interceptor/WmTokenInterceptor.java
  31. 17 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/mapper/WmChannelMapper.java
  32. 19 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/mapper/WmMaterialMapper.java
  33. 17 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/mapper/WmNewsMapper.java
  34. 31 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/mapper/WmNewsMaterialMapper.java
  35. 9 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/mapper/WmUserMapper.java
  36. 21 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/WmChannelService.java
  37. 40 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/WmMaterialService.java
  38. 38 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/WmNewsService.java
  39. 18 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/WmUserService.java
  40. 31 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/impl/WmChannelServiceImpl.java
  41. 110 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/impl/WmMaterialServiceImpl.java
  42. 405 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/impl/WmNewsServiceImpl.java
  43. 59 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/impl/WmUserServiceImpl.java
  44. 12 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/resources/bootstrap.yml
  45. 95 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/resources/logback.xml
  46. 13 0
      zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/resources/mapper/WmNewsMaterialMapper.xml

+ 20 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/pom.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.zhaozhaonews</groupId>
+        <artifactId>zhaozhaonews-service</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>zhaozhaonews-admin</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+</project>

+ 23 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/AdminApplication.java

@@ -0,0 +1,23 @@
+package com.zhaozhaonews.admin;
+
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title AdminApplication
+ * @description
+ * @create 2025/2/13
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+@MapperScan("com.zhaozhaonews.admin.mapper")
+public class AdminApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(AdminApplication.class, args);
+    }
+}

+ 18 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/config/MybatisPlusConfig.java

@@ -0,0 +1,18 @@
+package com.zhaozhaonews.admin.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MybatisPlusConfig {
+
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        return interceptor;
+    }
+}

+ 33 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/controller/AuthController.java

@@ -0,0 +1,33 @@
+package com.zhaozhaonews.admin.controller;
+
+import com.zhaozhaonews.admin.service.ApUserRealnameService;
+import com.zhaozhaonews.model.admin.dto.AuthDto;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/v1/auth")
+public class AuthController {
+
+    @Autowired
+    private ApUserRealnameService apUserRealnameService;
+
+    @PostMapping("/list")
+    public ResponseResult list(@RequestBody AuthDto dto) {
+        return apUserRealnameService.list(dto);
+    }
+
+    @PostMapping("/authFail")
+    public ResponseResult authFail(@RequestBody AuthDto dto) {
+        return apUserRealnameService.authFail(dto);
+    }
+
+    @PostMapping("/authPass")
+    public ResponseResult authPass(@RequestBody AuthDto dto) {
+        return apUserRealnameService.authPass(dto);
+    }
+}

+ 78 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/controller/ChannelController.java

@@ -0,0 +1,78 @@
+package com.zhaozhaonews.admin.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zhaozhaonews.admin.mapper.WmChannelMapper;
+import com.zhaozhaonews.model.admin.dto.ChannelDto;
+import com.zhaozhaonews.model.common.dto.PageResponseResult;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.wemedia.entity.WmChannel;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Date;
+
+@RestController
+@RequestMapping("/api/v1/channel")
+public class ChannelController {
+
+    @Autowired
+    private WmChannelMapper wmChannelMapper;
+
+    @PostMapping("/save")
+    public ResponseResult save(@RequestBody WmChannel adChannel) {
+        if (StringUtils.isBlank(adChannel.getName())) {
+            return ResponseResult.errorResult(500, "频道名称不能为空");
+        }
+        LambdaQueryWrapper<WmChannel> wrapper = new LambdaQueryWrapper<WmChannel>()
+                .eq(WmChannel::getName, adChannel.getName());
+        if (wmChannelMapper.selectCount(wrapper) > 0) {
+            return ResponseResult.errorResult(500, "频道名称不能重复");
+        }
+        if (adChannel.getId() == null) {
+            adChannel.setCreatedTime(new Date());
+            wmChannelMapper.insert(adChannel);
+        } else {
+            wmChannelMapper.updateById(adChannel);
+        }
+        return ResponseResult.okResult(null);
+    }
+
+    @PostMapping("/update")
+    public ResponseResult update(@RequestBody WmChannel adChannel) {
+        if (adChannel.getId() == null) {
+            return ResponseResult.errorResult(500, "ID不能为空");
+        }
+        wmChannelMapper.updateById(adChannel);
+        return ResponseResult.okResult(null);
+    }
+
+    @GetMapping("/del/{id}")
+    public ResponseResult del(@PathVariable Integer id) {
+        WmChannel channel = wmChannelMapper.selectById(id);
+        if (channel == null) {
+            return ResponseResult.errorResult(500, "数据不存在");
+        }
+        if (channel.getStatus() != null && channel.getStatus() == 1) {
+            return ResponseResult.errorResult(500, "只有禁用的频道才能删除");
+        }
+        wmChannelMapper.deleteById(id);
+        return ResponseResult.okResult(null);
+    }
+
+    @PostMapping("/list")
+    public ResponseResult list(@RequestBody ChannelDto dto) {
+        IPage<WmChannel> page = new Page<>(dto.getPage(), dto.getSize());
+        LambdaQueryWrapper<WmChannel> wrapper = new LambdaQueryWrapper<>();
+        if (StringUtils.isNotBlank(dto.getName())) {
+            wrapper.like(WmChannel::getName, dto.getName());
+        }
+        wrapper.orderByDesc(WmChannel::getCreatedTime);
+        IPage<WmChannel> result = wmChannelMapper.selectPage(page, wrapper);
+        PageResponseResult response = new PageResponseResult(dto.getPage(), dto.getSize(), (int) result.getTotal());
+        response.setData(result.getRecords());
+        return response;
+    }
+}

+ 21 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/controller/LoginController.java

@@ -0,0 +1,21 @@
+package com.zhaozhaonews.admin.controller;
+
+import com.zhaozhaonews.admin.service.AdUserService;
+import com.zhaozhaonews.model.admin.dto.AdUserDto;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class LoginController {
+
+    @Autowired
+    private AdUserService adUserService;
+
+    @PostMapping("/login/in")
+    public ResponseResult login(@RequestBody AdUserDto dto) {
+        return adUserService.login(dto);
+    }
+}

+ 123 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/controller/NewsController.java

@@ -0,0 +1,123 @@
+package com.zhaozhaonews.admin.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zhaozhaonews.admin.mapper.ApArticleConfigMapper;
+import com.zhaozhaonews.admin.mapper.ApArticleContentMapper;
+import com.zhaozhaonews.admin.mapper.ApArticleMapper;
+import com.zhaozhaonews.admin.mapper.WmNewsMapper;
+import com.zhaozhaonews.model.admin.dto.NewsAuthDto;
+import com.zhaozhaonews.model.article.entity.ApArticle;
+import com.zhaozhaonews.model.article.entity.ApArticleConfig;
+import com.zhaozhaonews.model.article.entity.ApArticleContent;
+import com.zhaozhaonews.model.common.dto.PageResponseResult;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.wemedia.entity.WmNews;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/v1/news")
+public class NewsController {
+
+    @Autowired
+    private WmNewsMapper wmNewsMapper;
+
+    @Autowired
+    private ApArticleMapper apArticleMapper;
+
+    @Autowired
+    private ApArticleConfigMapper apArticleConfigMapper;
+
+    @Autowired
+    private ApArticleContentMapper apArticleContentMapper;
+
+    @PostMapping("/list_vo")
+    public ResponseResult listVo(@RequestBody NewsAuthDto dto) {
+        IPage<WmNews> page = new Page<>(dto.getPage(), dto.getSize());
+        LambdaQueryWrapper<WmNews> wrapper = new LambdaQueryWrapper<>();
+        if (dto.getStatus() != null) {
+            wrapper.eq(WmNews::getStatus, dto.getStatus());
+        }
+        if (StringUtils.isNotBlank(dto.getTitle())) {
+            wrapper.like(WmNews::getTitle, dto.getTitle());
+        }
+        wrapper.orderByDesc(WmNews::getCreatedTime);
+        IPage<WmNews> result = wmNewsMapper.selectPage(page, wrapper);
+        PageResponseResult response = new PageResponseResult(dto.getPage(), dto.getSize(), (int) result.getTotal());
+        response.setData(result.getRecords());
+        return response;
+    }
+
+    @GetMapping("/one_vo/{id}")
+    public ResponseResult oneVo(@PathVariable Integer id) {
+        WmNews news = wmNewsMapper.selectById(id);
+        if (news == null) {
+            return ResponseResult.errorResult(500, "数据不存在");
+        }
+        return ResponseResult.okResult(news);
+    }
+
+    @PostMapping("/auth_fail")
+    public ResponseResult authFail(@RequestBody NewsAuthDto dto) {
+        if (dto.getId() == null) {
+            return ResponseResult.errorResult(500, "ID不能为空");
+        }
+        WmNews news = wmNewsMapper.selectById(dto.getId());
+        if (news == null) {
+            return ResponseResult.errorResult(500, "数据不存在");
+        }
+        news.setStatus((short) 2);
+        news.setReason(dto.getMsg());
+        wmNewsMapper.updateById(news);
+        return ResponseResult.okResult(null);
+    }
+
+    @PostMapping("/auth_pass")
+    public ResponseResult authPass(@RequestBody NewsAuthDto dto) {
+        if (dto.getId() == null) {
+            return ResponseResult.errorResult(500, "ID不能为空");
+        }
+        WmNews news = wmNewsMapper.selectById(dto.getId());
+        if (news == null) {
+            return ResponseResult.errorResult(500, "数据不存在");
+        }
+
+        Date now = new Date();
+
+        ApArticle article = new ApArticle();
+        article.setTitle(news.getTitle());
+        article.setChannelId(news.getChannelId());
+        article.setLayout(news.getType() != null ? news.getType() : 0);
+        article.setCreatedTime(now);
+        article.setPublishTime(now);
+        article.setAuthorId(news.getUserId().longValue());
+        article.setImages(news.getImages());
+        apArticleMapper.insert(article);
+        Long articleId = article.getId();
+
+        ApArticleConfig config = new ApArticleConfig(articleId);
+        apArticleConfigMapper.insert(config);
+
+        ApArticleContent content = new ApArticleContent();
+        content.setArticleId(articleId);
+        content.setContent(news.getContent());
+        apArticleContentMapper.insert(content);
+
+        news.setArticleId(articleId);
+        news.setPublishTime(now);
+        news.setStatus((short) 9);
+        news.setReason(dto.getMsg());
+        wmNewsMapper.updateById(news);
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("articleId", articleId);
+        return ResponseResult.okResult(data);
+    }
+}

+ 7 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/AdUserMapper.java

@@ -0,0 +1,7 @@
+package com.zhaozhaonews.admin.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhaozhaonews.model.admin.entity.AdUser;
+
+public interface AdUserMapper extends BaseMapper<AdUser> {
+}

+ 7 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/ApArticleConfigMapper.java

@@ -0,0 +1,7 @@
+package com.zhaozhaonews.admin.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhaozhaonews.model.article.entity.ApArticleConfig;
+
+public interface ApArticleConfigMapper extends BaseMapper<ApArticleConfig> {
+}

+ 7 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/ApArticleContentMapper.java

@@ -0,0 +1,7 @@
+package com.zhaozhaonews.admin.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhaozhaonews.model.article.entity.ApArticleContent;
+
+public interface ApArticleContentMapper extends BaseMapper<ApArticleContent> {
+}

+ 7 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/ApArticleMapper.java

@@ -0,0 +1,7 @@
+package com.zhaozhaonews.admin.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhaozhaonews.model.article.entity.ApArticle;
+
+public interface ApArticleMapper extends BaseMapper<ApArticle> {
+}

+ 7 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/ApUserRealnameMapper.java

@@ -0,0 +1,7 @@
+package com.zhaozhaonews.admin.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhaozhaonews.model.admin.entity.ApUserRealname;
+
+public interface ApUserRealnameMapper extends BaseMapper<ApUserRealname> {
+}

+ 7 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/WmChannelMapper.java

@@ -0,0 +1,7 @@
+package com.zhaozhaonews.admin.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhaozhaonews.model.wemedia.entity.WmChannel;
+
+public interface WmChannelMapper extends BaseMapper<WmChannel> {
+}

+ 7 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/mapper/WmNewsMapper.java

@@ -0,0 +1,7 @@
+package com.zhaozhaonews.admin.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhaozhaonews.model.wemedia.entity.WmNews;
+
+public interface WmNewsMapper extends BaseMapper<WmNews> {
+}

+ 8 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/service/AdUserService.java

@@ -0,0 +1,8 @@
+package com.zhaozhaonews.admin.service;
+
+import com.zhaozhaonews.model.admin.dto.AdUserDto;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+
+public interface AdUserService {
+    ResponseResult login(AdUserDto dto);
+}

+ 10 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/service/ApUserRealnameService.java

@@ -0,0 +1,10 @@
+package com.zhaozhaonews.admin.service;
+
+import com.zhaozhaonews.model.admin.dto.AuthDto;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+
+public interface ApUserRealnameService {
+    ResponseResult list(AuthDto dto);
+    ResponseResult authFail(AuthDto dto);
+    ResponseResult authPass(AuthDto dto);
+}

+ 44 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/service/impl/AdUserServiceImpl.java

@@ -0,0 +1,44 @@
+package com.zhaozhaonews.admin.service.impl;
+
+import com.zhaozhaonews.admin.mapper.AdUserMapper;
+import com.zhaozhaonews.admin.service.AdUserService;
+import com.zhaozhaonews.model.admin.dto.AdUserDto;
+import com.zhaozhaonews.model.admin.entity.AdUser;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.util.common.AppJwtUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.DigestUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class AdUserServiceImpl implements AdUserService {
+
+    @Autowired
+    private AdUserMapper adUserMapper;
+
+    @Override
+    public ResponseResult login(AdUserDto dto) {
+        if (dto.getName() == null || dto.getPassword() == null) {
+            return ResponseResult.errorResult(500, "用户名或密码不能为空");
+        }
+        AdUser adUser = adUserMapper.selectOne(
+                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<AdUser>()
+                        .eq(AdUser::getName, dto.getName()));
+        if (adUser == null) {
+            return ResponseResult.errorResult(500, "数据不存在");
+        }
+        String salt = adUser.getSalt();
+        String encryptedPassword = DigestUtils.md5DigestAsHex((dto.getPassword() + salt).getBytes());
+        if (!encryptedPassword.equalsIgnoreCase(adUser.getPassword())) {
+            return ResponseResult.errorResult(500, "密码错误");
+        }
+        String token = AppJwtUtil.getToken(adUser.getId().longValue());
+        Map<String, Object> data = new HashMap<>();
+        data.put("token", token);
+        data.put("user", adUser);
+        return ResponseResult.okResult(data);
+    }
+}

+ 55 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/java/com/zhaozhaonews/admin/service/impl/ApUserRealnameServiceImpl.java

@@ -0,0 +1,55 @@
+package com.zhaozhaonews.admin.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zhaozhaonews.admin.mapper.ApUserRealnameMapper;
+import com.zhaozhaonews.admin.service.ApUserRealnameService;
+import com.zhaozhaonews.model.admin.dto.AuthDto;
+import com.zhaozhaonews.model.admin.entity.ApUserRealname;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ApUserRealnameServiceImpl implements ApUserRealnameService {
+
+    @Autowired
+    private ApUserRealnameMapper apUserRealnameMapper;
+
+    @Override
+    public ResponseResult list(AuthDto dto) {
+        IPage<ApUserRealname> page = new Page<>(dto.getPage(), dto.getSize());
+        LambdaQueryWrapper<ApUserRealname> wrapper = new LambdaQueryWrapper<>();
+        if (dto.getStatus() != null) {
+            wrapper.eq(ApUserRealname::getStatus, dto.getStatus());
+        }
+        wrapper.orderByDesc(ApUserRealname::getCreatedTime);
+        IPage<ApUserRealname> result = apUserRealnameMapper.selectPage(page, wrapper);
+        return ResponseResult.okResult(result);
+    }
+
+    @Override
+    public ResponseResult authFail(AuthDto dto) {
+        ApUserRealname record = apUserRealnameMapper.selectById(dto.getId());
+        if (record == null) {
+            return ResponseResult.errorResult(500, "数据不存在");
+        }
+        record.setStatus((short) 2);
+        record.setReason(dto.getMsg());
+        apUserRealnameMapper.updateById(record);
+        return ResponseResult.okResult(null);
+    }
+
+    @Override
+    public ResponseResult authPass(AuthDto dto) {
+        ApUserRealname record = apUserRealnameMapper.selectById(dto.getId());
+        if (record == null) {
+            return ResponseResult.errorResult(500, "数据不存在");
+        }
+        record.setStatus((short) 9);
+        record.setReason(dto.getMsg());
+        apUserRealnameMapper.updateById(record);
+        return ResponseResult.okResult(null);
+    }
+}

+ 12 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/resources/bootstrap.yml

@@ -0,0 +1,12 @@
+server:
+  port: 51804
+spring:
+  application:
+    name: zhaozhaonews-admin
+  cloud:
+    nacos:
+      discovery:
+        server-addr: 192.168.200.129:8848
+      config:
+        server-addr: 192.168.200.129:8848
+        file-extension: yaml

+ 58 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-admin/src/main/resources/logback-spring.xml

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration debug="false" scan="true" scanPeriod="30 seconds">
+    <springProperty name="APP_NAME" source="spring.application.name" defaultValue="zhaozhaonews" />
+    <property name="LOG_HOME" value="${user.dir}/logs"/>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}) - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_HOME}/${APP_NAME}.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <FileNamePattern>${LOG_HOME}/${APP_NAME}.log.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
+            <MaxFileSize>10MB</MaxFileSize>
+            <MaxHistory>30</MaxHistory>
+            <totalSizeCap>1GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_HOME}/error.log</file>
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>ERROR</level>
+        </filter>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <FileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.log</FileNamePattern>
+            <MaxHistory>30</MaxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <logger name="com.baomidou.mybatisplus" level="DEBUG"/>
+    <logger name="org.springframework" level="INFO"/>
+    <logger name="org.springframework.web" level="INFO"/>
+    <logger name="springfox" level="WARN"/>
+    <logger name="io.swagger" level="WARN"/>
+    <logger name="com.zaxxer.hikari" level="INFO"/>
+    <logger name="java.sql.Connection" level="DEBUG"/>
+    <logger name="java.sql.Statement" level="DEBUG"/>
+    <logger name="java.sql.PreparedStatement" level="DEBUG"/>
+    <logger name="java.sql.ResultSet" level="DEBUG"/>
+
+    <root level="INFO">
+        <appender-ref ref="STDOUT"/>
+        <appender-ref ref="FILE"/>
+        <appender-ref ref="ERROR_FILE"/>
+    </root>
+</configuration>

+ 33 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/pom.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.zhaozhaonews</groupId>
+        <artifactId>zhaozhaonews-service</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>zhaozhaonews-wemedia</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.zhaozhaonews</groupId>
+            <artifactId>zhaozhaonews-file-starter</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <!--alibaba提供的解析JSON的库 FastJson-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 47 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/WeMediaApplication.java

@@ -0,0 +1,47 @@
+package com.zhaozhaonews.wemedia;
+
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title AdminApplication
+ * @description
+ * @create 2025/2/13
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+@EnableFeignClients(basePackages = "com.zhaozhaonews.apis")
+@MapperScan("com.zhaozhaonews.wemedia.mapper")
+public class WeMediaApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(WeMediaApplication.class, args);
+    }
+
+    /**
+     * 配置Mybatis-Plus分页插件
+     * Mybatis-Plus分页功能需要使用这个插件才能生效
+     * 把它声明为@Bean , Spring就会自动注入到Mybatis中
+     * 当我们需要分页的时候就可以使用了比如新闻列表、素材列表
+     * @return
+     */
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        //创建Mybatis-Plus拦截器
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        //添加分页插件PaginationInnerInterceptor是内置的分页实现
+        //DbType.MYSQL 设置数据库类型为MySQL
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        return interceptor;
+    }
+
+}

+ 44 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/config/GlobalExceptionHandler.java

@@ -0,0 +1,44 @@
+package com.zhaozhaonews.wemedia.config;
+
+import com.zhaozhaonews.common.exception.CustomException;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * 全局异常处理器
+ *
+ * 基于 @RestControllerAdvice 实现统一异常处理。
+ * 捕获业务异常(CustomException)返回对应 HTTP 状态码和错误信息,
+ * 捕获其他未预期异常返回 503 状态码和异常消息。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    /**
+     * 处理自定义业务异常
+     *
+     * @param e 自定义业务异常,包含错误枚举信息
+     * @return 标准错误响应
+     */
+    @ExceptionHandler(CustomException.class)
+    public ResponseResult handleCustomException(CustomException e) {
+        return ResponseResult.errorResult(e.getAppHttpCodeEnum());
+    }
+
+    /**
+     * 处理所有未预期的异常
+     *
+     * @param e 异常对象
+     * @return 503 状态码的错误响应
+     */
+    @ExceptionHandler(Exception.class)
+    public ResponseResult handleException(Exception e) {
+        e.printStackTrace();
+        return ResponseResult.errorResult(503, e.getMessage());
+    }
+}

+ 39 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/config/WebMvcConfig.java

@@ -0,0 +1,39 @@
+package com.zhaozhaonews.wemedia.config;
+
+
+import com.zhaozhaonews.wemedia.interceptor.WmTokenInterceptor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title WebMvcConfig
+ * @description SpringMVC配置类
+ * @create 2026/5/10
+ *
+ * 实现WebMvcConfigurer接口,重写addInterceptors方法,添加拦截器
+ * 拦截器的作用是拦截请求,进行一些处理,比如登录验证、权限验证等等,我们可以自定义:
+ *  |-拦截器注册
+ *  |-静态资源映射
+ *  |-跨域配置
+ *  |-消息转换器等等
+ * 使用@Configuration注解,让Spring会自动加载这个配置类
+ */
+@Configuration
+@Slf4j
+public class WebMvcConfig implements WebMvcConfigurer {
+    /**
+     * 注册拦截器
+     * @param registry
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(new WmTokenInterceptor())   //添加拦截器
+                .addPathPatterns("/**")    //👈拦截所有请求
+                .excludePathPatterns("/**/login/**");    //👈排除登录请求
+        log.info("注册拦截器成功.............");
+    }
+}

+ 23 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/controller/LoginController.java

@@ -0,0 +1,23 @@
+package com.zhaozhaonews.wemedia.controller;
+
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.wemedia.dto.WmLoginDto;
+import com.zhaozhaonews.wemedia.service.WmUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/login")
+public class LoginController {
+
+    @Autowired
+    private WmUserService wmUserService;
+
+    @PostMapping("/in")
+    public ResponseResult login(@RequestBody WmLoginDto dto){
+        return wmUserService.login(dto);
+    }
+}

+ 74 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/controller/StatisticsController.java

@@ -0,0 +1,74 @@
+package com.zhaozhaonews.wemedia.controller;
+
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.wemedia.entity.WmNews;
+import com.zhaozhaonews.wemedia.mapper.WmNewsMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+@RestController
+@RequestMapping("/api/v1/statistics")
+public class StatisticsController {
+
+    @Autowired
+    private StringRedisTemplate redisTemplate;
+
+    @Autowired
+    private WmNewsMapper wmNewsMapper;
+
+    @GetMapping("/overview/{userId}")
+    public ResponseResult overview(@PathVariable Integer userId) {
+        Map<String, Object> result = new HashMap<>();
+
+        Long totalReads = 0L;
+        Long totalLikes = 0L;
+        Long totalCollections = 0L;
+        Long totalComments = 0L;
+
+        List<WmNews> newsList = wmNewsMapper.selectList(
+                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<WmNews>()
+                        .eq(WmNews::getUserId, userId));
+        for (WmNews news : newsList) {
+            if (news.getArticleId() != null) {
+                String readKey = "behavior:read:" + news.getArticleId();
+                Map<Object, Object> readData = redisTemplate.opsForHash().entries(readKey);
+                totalReads += readData.values().stream()
+                        .mapToLong(v -> Long.parseLong(v.toString()))
+                        .sum();
+
+                Long likes = redisTemplate.opsForSet().size("behavior:likes:" + news.getArticleId());
+                totalLikes += likes;
+
+                result.put("article_" + news.getArticleId() + "_likes", likes);
+            }
+        }
+
+        Long followers = redisTemplate.opsForSet().size("behavior:follow:" + userId);
+
+        result.put("totalReads", totalReads);
+        result.put("totalLikes", totalLikes);
+        result.put("totalCollections", totalCollections);
+        result.put("totalComments", totalComments);
+        result.put("followers", followers);
+        result.put("articleCount", (long) newsList.size());
+        return ResponseResult.okResult(result);
+    }
+
+    @GetMapping("/article/{articleId}")
+    public ResponseResult articleStats(@PathVariable Long articleId) {
+        Map<String, Object> result = new HashMap<>();
+
+        Map<Object, Object> readData = redisTemplate.opsForHash().entries("behavior:read:" + articleId);
+        Long totalReads = readData.values().stream()
+                .mapToLong(v -> Long.parseLong(v.toString()))
+                .sum();
+        Long likes = redisTemplate.opsForSet().size("behavior:likes:" + articleId);
+
+        result.put("reads", totalReads);
+        result.put("likes", likes);
+        return ResponseResult.okResult(result);
+    }
+}

+ 34 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/controller/WmChannelController.java

@@ -0,0 +1,34 @@
+package com.zhaozhaonews.wemedia.controller;
+
+
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.wemedia.service.WmChannelService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title WmChannelController
+ * @description
+ * @create 2026/5/10
+ */
+@RestController
+@RequestMapping("/api/v1/channel")
+public class WmChannelController {
+
+    @Autowired
+    private WmChannelService wmChannelService;
+
+    /**
+     * 查询所有频道
+     * @return
+     */
+    @GetMapping("/channels")
+    public ResponseResult findAllChannels(){
+        return wmChannelService.findAll();
+    }
+
+}

+ 52 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/controller/WmMaterialController.java

@@ -0,0 +1,52 @@
+package com.zhaozhaonews.wemedia.controller;
+
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.wemedia.dto.WmMaterialDto;
+import com.zhaozhaonews.wemedia.service.WmMaterialService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 素材控制器
+ *
+ * 提供自媒体端素材管理相关的 REST 接口。
+ * 支持图片素材的上传和分页列表查询,素材文件存储在 MinIO 中。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@RestController
+@RequestMapping("/api/v1/material")
+public class WmMaterialController {
+
+    @Autowired
+    private WmMaterialService wmMaterialService;
+
+    /**
+     * 上传图片素材
+     *
+     * @param multipartFile 上传的图片文件
+     * @return 包含素材信息的响应结果
+     */
+    @PostMapping("/upload_picture")
+    public ResponseResult uploadPicture(@RequestParam("multipartFile") MultipartFile multipartFile) {
+        return wmMaterialService.uploadPicture(multipartFile);
+    }
+
+    /**
+     * 分页查询素材列表
+     *
+     * @param dto 素材查询 DTO,包含分页参数和收藏筛选条件
+     * @return 分页素材数据
+     */
+    @PostMapping("/list")
+    public ResponseResult findList(@RequestBody WmMaterialDto dto) {
+        return wmMaterialService.findList(dto);
+    }
+}

+ 56 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/controller/WmNewsController.java

@@ -0,0 +1,56 @@
+package com.zhaozhaonews.wemedia.controller;
+
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.wemedia.dto.WmNewsDto;
+import com.zhaozhaonews.model.wemedia.dto.WmNewsPageReqDto;
+import com.zhaozhaonews.wemedia.service.WmNewsService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 自媒体文章信息 Controller
+ *
+ * @author zhaozhaonews
+ * @description 处理自媒体文章相关的请求
+ * @create 2026/5/10
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/v1/news")
+public class WmNewsController {
+
+    @Autowired
+    private WmNewsService wmNewsService;
+
+    /**
+     * 查询文章列表
+     * @param dto
+     * @return
+     */
+    @PostMapping("/list")
+    public ResponseResult findAll(@RequestBody WmNewsPageReqDto dto) {
+        return wmNewsService.findAll(dto);
+    }
+
+    /**
+     * 查询新闻详情
+     * @param id
+     * @return
+     */
+    @GetMapping("/one/{id}")
+    public ResponseResult findById(@PathVariable Integer id) {
+        return wmNewsService.findById(id);
+    }
+
+    /**
+     * 发布/修改新闻 或保存草稿
+     *
+     * @param dto
+     * @return
+     */
+    @PostMapping("/submit")
+    public ResponseResult submitNews(@RequestBody WmNewsDto dto) {
+        return wmNewsService.submitNews(dto);
+    }
+}

+ 78 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/interceptor/WmTokenInterceptor.java

@@ -0,0 +1,78 @@
+package com.zhaozhaonews.wemedia.interceptor;
+
+
+import com.zhaozhaonews.model.wemedia.entity.WmUser;
+import com.zhaozhaonews.util.thread.WmThreadLocalUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title WmTokenInterceptor
+ * @description 自媒体端token拦截器
+ * 实现HandlerInterceptor接口,重写:
+ * |-preHandle方法   执行前调用
+ * |-postHandle方法  执行后调用
+ * |-afterCompletion方法  整个请求完成后调用,用来清理ThreadLocal
+ * <p>
+ * 执行的时机:
+ * preHandle方法--从Header里面读取用户信息,查用户,存入ThreadLocal
+ * 👇
+ * Controller方法  执行业务功能
+ * 👇
+ * afterCompletion方法  清理ThreadLocal
+ * @create 2026/5/10
+ */
+@Slf4j
+public class WmTokenInterceptor implements HandlerInterceptor {
+    /**
+     * Controller执行前调用
+     *
+     * @param request  传统的HttpRequest对象-请求
+     * @param response 传统的HttpResponse对象-响应
+     * @param handler  当前要执行的Controller方法信息
+     * @return
+     * @throws Exception
+     */
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        //======1、从header里面读取userId======
+        String userId = request.getHeader("userId");
+        //做非空判定--其实不判定也行,因为网关已经帮我们鉴权过了
+        if (userId != null) {
+            {
+                //======2、构造用户对象并存入到ThreadLocal=====
+                //实际的义务中这里会根据userId查询数据库,获取完整的用户信息,再操作
+                //但是当前的拦截器其实只需要userId,所以这里我们直接构造一个User对象,并设置userId
+                WmUser user = new WmUser();
+                user.setId(Integer.valueOf(userId));
+                WmThreadLocalUtil.setUser(user);
+                log.info("用户{}已存入 ThreadLocal", userId);
+            }
+        }
+        //======3、放行请求==========
+        //注意:这里就不要再拦截任何请求,只是读取userId并存入ThreadLocal,鉴权操作已经在网关实现完了
+        return true;
+    }
+
+    /**
+     * 请求完成后的清理工作
+     * 无论Controller是否抛出异常,当前的方法都会执行
+     * 在这里清理ThreadLocal,防止内存泄漏和现场服用带来的数据污染
+     * @param request
+     * @param response
+     * @param handler
+     * @param ex
+     * @throws Exception
+     */
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+        //清理ThreadLocal中的用户信息
+        WmThreadLocalUtil.clear();
+        log.debug("用户信息已从 ThreadLocal 中清除");
+    }
+}

+ 17 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/mapper/WmChannelMapper.java

@@ -0,0 +1,17 @@
+package com.zhaozhaonews.wemedia.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhaozhaonews.model.wemedia.entity.WmChannel;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title WmChannelMapper
+ * @description 频道Mapper
+ * @create 2026/5/10
+ */
+@Mapper
+public interface WmChannelMapper extends BaseMapper<WmChannel> {
+}

+ 19 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/mapper/WmMaterialMapper.java

@@ -0,0 +1,19 @@
+package com.zhaozhaonews.wemedia.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhaozhaonews.model.wemedia.entity.WmMaterial;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 素材 Mapper 接口
+ *
+ * 提供素材表的基础 CRUD 操作,继承 MyBatis-Plus 的 BaseMapper。
+ * 素材信息包括上传用户、文件 URL、素材类型和收藏状态等字段。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Mapper
+public interface WmMaterialMapper extends BaseMapper<WmMaterial> {
+}

+ 17 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/mapper/WmNewsMapper.java

@@ -0,0 +1,17 @@
+package com.zhaozhaonews.wemedia.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhaozhaonews.model.wemedia.entity.WmNews;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 自媒体文章信息 Mapper 接口
+ *
+ * @author zhaozhaonews
+ * @description 针对表 wm_news 的数据库操作接口
+ * @create 2026/5/10
+ */
+@Mapper
+public interface WmNewsMapper extends BaseMapper<WmNews> {
+
+}

+ 31 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/mapper/WmNewsMaterialMapper.java

@@ -0,0 +1,31 @@
+package com.zhaozhaonews.wemedia.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhaozhaonews.model.wemedia.entity.WmNewsMaterial;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title WmNewsMaterialMapper
+ * @description
+ * @create 2026/5/17
+ */
+@Mapper
+public interface WmNewsMaterialMapper extends BaseMapper<WmNewsMaterial> {
+    /**
+     * 批量保存新闻和素材的关联关系
+     * @param materialIds   素材ID列表 按照图片在新闻中出现的顺序排序。
+     * @param newsId        新闻ID
+     * @param type      引用类型(0=内容,1=封面)
+     */
+    void saveRelations(
+            @Param("materialIds") List<Integer> materialIds,
+            @Param("newsId") Integer newsId,
+            @Param("type") Short type
+    );
+}

+ 9 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/mapper/WmUserMapper.java

@@ -0,0 +1,9 @@
+package com.zhaozhaonews.wemedia.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhaozhaonews.model.wemedia.entity.WmUser;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface WmUserMapper extends BaseMapper<WmUser> {
+}

+ 21 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/WmChannelService.java

@@ -0,0 +1,21 @@
+package com.zhaozhaonews.wemedia.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.wemedia.entity.WmChannel;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title WmChannelService
+ * @description 自媒体频道Service
+ * @create 2026/5/10
+ */
+public interface WmChannelService extends IService<WmChannel> {
+    /**
+     * 查询所有频道
+     * @return
+     */
+    ResponseResult findAll();
+}

+ 40 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/WmMaterialService.java

@@ -0,0 +1,40 @@
+package com.zhaozhaonews.wemedia.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.wemedia.dto.WmMaterialDto;
+import com.zhaozhaonews.model.wemedia.entity.WmMaterial;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 素材服务接口
+ *
+ * 定义素材管理的业务方法,继承 MyBatis-Plus 的 IService
+ * 获得素材表的基础 CRUD 操作能力。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+public interface WmMaterialService extends IService<WmMaterial> {
+
+    /**
+     * 上传图片素材
+     *
+     * 将图片文件上传到 MinIO 文件存储服务,并保存素材记录到数据库。
+     *
+     * @param multipartFile 上传的图片文件
+     * @return 包含素材信息的响应结果
+     */
+    ResponseResult uploadPicture(MultipartFile multipartFile);
+
+    /**
+     * 分页查询素材列表
+     *
+     * 支持按收藏状态筛选,查询当前自媒体用户的素材列表。
+     *
+     * @param dto 素材查询 DTO,包含分页参数和收藏筛选条件
+     * @return 分页素材数据
+     */
+    ResponseResult findList(WmMaterialDto dto);
+}

+ 38 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/WmNewsService.java

@@ -0,0 +1,38 @@
+package com.zhaozhaonews.wemedia.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.wemedia.dto.WmNewsDto;
+import com.zhaozhaonews.model.wemedia.dto.WmNewsPageReqDto;
+import com.zhaozhaonews.model.wemedia.entity.WmNews;
+
+/**
+ * 自媒体文章信息 Service 接口
+ *
+ * @author zhaozhaonews
+ * @description 针对表 wm_news 的业务逻辑接口
+ * @create 2026/5/10
+ */
+public interface WmNewsService extends IService<WmNews> {
+    /**
+     * 查询所有文章(多条件筛选)
+     * @param dto   查询条件
+     * @return  分页结果
+     */
+    ResponseResult findAll(WmNewsPageReqDto dto);
+
+
+    /**
+     * 根据id查询文章
+     * @param id 文章id
+     * @return  文章详情
+     */
+    ResponseResult findById(Integer id);
+
+    /**
+     * 发布/修改新闻 或 保存草稿
+     * @param dto 新闻提交的数据
+     * @return  操作结果
+     */
+    ResponseResult submitNews(WmNewsDto dto);
+}

+ 18 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/WmUserService.java

@@ -0,0 +1,18 @@
+package com.zhaozhaonews.wemedia.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.wemedia.dto.WmLoginDto;
+import com.zhaozhaonews.model.wemedia.entity.WmUser;
+
+public interface WmUserService extends IService<WmUser> {
+
+    /**
+     * 自媒体端登录
+     * @param dto
+     * @return
+     */
+    public ResponseResult login(WmLoginDto dto);
+
+}

+ 31 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/impl/WmChannelServiceImpl.java

@@ -0,0 +1,31 @@
+package com.zhaozhaonews.wemedia.service.impl;
+
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.wemedia.entity.WmChannel;
+import com.zhaozhaonews.wemedia.mapper.WmChannelMapper;
+import com.zhaozhaonews.wemedia.service.WmChannelService;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title WmChannelServiceImpl
+ * @description
+ * @create 2026/5/10
+ */
+@Service
+public class WmChannelServiceImpl extends ServiceImpl<WmChannelMapper, WmChannel> implements WmChannelService {
+
+    /**
+     * 查询所有频道
+     * 实现逻辑非常简单,
+     * 直接调用Mybatis-Plus的list方法即可。
+     * @return
+     */
+    @Override
+    public ResponseResult findAll() {
+        return ResponseResult.okResult(list());
+    }
+}

+ 110 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/impl/WmMaterialServiceImpl.java

@@ -0,0 +1,110 @@
+package com.zhaozhaonews.wemedia.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhaozhaonews.file.service.FileStorageService;
+import com.zhaozhaonews.model.common.dto.PageResponseResult;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.common.enums.AppHttpCodeEnum;
+import com.zhaozhaonews.model.wemedia.dto.WmMaterialDto;
+import com.zhaozhaonews.model.wemedia.entity.WmMaterial;
+import com.zhaozhaonews.util.thread.WmThreadLocalUtil;
+import com.zhaozhaonews.wemedia.mapper.WmMaterialMapper;
+import com.zhaozhaonews.wemedia.service.WmMaterialService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * 素材服务实现类
+ *
+ * 实现图片素材的上传和分页查询功能:
+ * - 上传:使用 FileStorageService 将图片存储到 MinIO,同时保存素材记录到数据库
+ * - 查询:按用户维度分页查询素材列表,支持收藏状态筛选
+ *
+ * 当前用户信息从 WmThreadLocalUtil 获取(由 WmTokenInterceptor 注入)。
+ *
+ * @author WanJl
+ * @version 1.0
+ * @since 2026/04/19
+ */
+@Slf4j
+@Service
+@Transactional
+public class WmMaterialServiceImpl extends ServiceImpl<WmMaterialMapper, WmMaterial> implements WmMaterialService {
+
+    @Autowired
+    private FileStorageService fileStorageService;
+
+    /**
+     * 上传图片素材
+     *
+     * 处理流程:
+     * 1. 校验上传文件不为空
+     * 2. 使用 UUID 生成唯一文件名
+     * 3. 通过 FileStorageService 上传到 MinIO
+     * 4. 保存素材记录到数据库(包含用户 ID、URL、收藏状态等)
+     *
+     * @param multipartFile 上传的图片文件
+     * @return 包含素材信息的响应结果
+     */
+    @Override
+    public ResponseResult uploadPicture(MultipartFile multipartFile) {
+        if (multipartFile == null || multipartFile.getSize() == 0) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
+        }
+
+        String fileName = UUID.randomUUID().toString().replace("-", "");
+        String originalFilename = multipartFile.getOriginalFilename();
+        String postfix = originalFilename.substring(originalFilename.lastIndexOf("."));
+        String fileId = null;
+        try {
+            fileId = fileStorageService.uploadImgFile("", fileName + postfix, multipartFile.getInputStream());
+            log.info("上传图片到MinIO中,fileId:{}", fileId);
+        } catch (IOException e) {
+            log.error("WmMaterialServiceImpl-上传文件失败", e);
+            return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "上传文件失败");
+        }
+
+        WmMaterial wmMaterial = new WmMaterial();
+        wmMaterial.setUserId(WmThreadLocalUtil.getUser().getId());
+        wmMaterial.setUrl(fileId);
+        wmMaterial.setIsCollection((short) 0);
+        wmMaterial.setType((short) 0);
+        wmMaterial.setCreatedTime(new Date());
+        save(wmMaterial);
+
+        return ResponseResult.okResult(wmMaterial);
+    }
+
+    /**
+     * 分页查询素材列表
+     *
+     * 支持按收藏状态筛选,查询当前用户的素材并按创建时间倒序排列。
+     *
+     * @param dto 素材查询 DTO
+     * @return 分页素材数据
+     */
+    @Override
+    public ResponseResult findList(WmMaterialDto dto) {
+        dto.checkParam();
+        Page page = new Page(dto.getPage(), dto.getSize());
+        LambdaQueryWrapper<WmMaterial> wrapper = new LambdaQueryWrapper<>();
+        if (dto.getIsCollection() != null && dto.getIsCollection() == 1) {
+            wrapper.eq(WmMaterial::getIsCollection, dto.getIsCollection());
+        }
+        wrapper.eq(WmMaterial::getUserId, WmThreadLocalUtil.getUser().getId());
+        wrapper.orderByDesc(WmMaterial::getCreatedTime);
+        page = page(page, wrapper);
+        ResponseResult result = new PageResponseResult(dto.getPage(), dto.getSize(), (int) page.getTotal());
+        result.setData(page.getRecords());
+        return result;
+    }
+}

+ 405 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/impl/WmNewsServiceImpl.java

@@ -0,0 +1,405 @@
+package com.zhaozhaonews.wemedia.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhaozhaonews.common.constants.WemediaConstants;
+import com.zhaozhaonews.common.exception.CustomException;
+import com.zhaozhaonews.model.common.dto.PageResponseResult;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.common.enums.AppHttpCodeEnum;
+import com.zhaozhaonews.model.wemedia.dto.WmNewsDto;
+import com.zhaozhaonews.model.wemedia.dto.WmNewsPageReqDto;
+import com.zhaozhaonews.model.wemedia.entity.WmMaterial;
+import com.zhaozhaonews.model.wemedia.entity.WmNews;
+import com.zhaozhaonews.model.wemedia.entity.WmNewsMaterial;
+import com.zhaozhaonews.model.wemedia.entity.WmUser;
+import com.zhaozhaonews.util.thread.WmThreadLocalUtil;
+import com.zhaozhaonews.wemedia.mapper.WmMaterialMapper;
+import com.zhaozhaonews.wemedia.mapper.WmNewsMapper;
+import com.zhaozhaonews.wemedia.mapper.WmNewsMaterialMapper;
+import com.zhaozhaonews.wemedia.service.WmNewsService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 自媒体文章信息 Service 实现类
+ *
+ * @author zhaozhaonews
+ * @description 针对表 wm_news 的业务逻辑实现
+ * @create 2026/5/10
+ */
+@Slf4j
+@Service
+@Transactional
+public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService {
+
+    /**
+     * 查询所有文章(多条件筛选)
+     * 条件拼接规则:
+     * 所有查询条件都是“可选”的,前端传什么,就过滤什么
+     * LambdaQueryWrapper<WmNews> queryWrapper = new LambdaQueryWrapper<>();
+     * 它支持链式调用,可以动态拼接SQL语句
+     *
+     * @param dto 查询条件
+     * @return 分页结果
+     */
+    @Override
+    public ResponseResult findAll(WmNewsPageReqDto dto) {
+        //1、参数校验
+        if (dto == null) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);   //参数无效
+        }
+        //2、校验并初始化分页参数
+        dto.checkParam();
+
+        //3、获取当前登录用户
+        WmUser user = WmThreadLocalUtil.getUser();
+        if (user == null) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);   //需要登录
+        }
+        //4、创建分页对象
+        Page<WmNews> page = new Page<>(dto.getPage(), dto.getSize());
+
+        //5、构建动态查询条件
+        LambdaQueryWrapper<WmNews> queryWrapper = new LambdaQueryWrapper<>();
+
+        //条件1:按照状态筛选 status不为空的时候就添加
+        if (dto.getStatus() != null) {
+            // WHERE status = ?
+            queryWrapper.eq(WmNews::getStatus, dto.getStatus());
+        }
+
+        //条件2:按照关键字筛选 keyword不为空的时候就添加
+        if (StringUtils.isNotBlank(dto.getKeyword())) {
+            // WHERE title LIKE ?
+            queryWrapper.like(WmNews::getTitle, dto.getKeyword());
+        }
+        //条件3:按照发布时间筛选 发布开始时间(范围查询起始时间)和发布结束时间(范围查询结束时间)都不为空时添加 BETWEEN
+        if (dto.getBeginPubDate() != null && dto.getEndPubDate() != null) {
+            // WHERE publish_time BETWEEN ? AND ?
+            queryWrapper.between(WmNews::getPublishTime, dto.getBeginPubDate(), dto.getEndPubDate());
+        }
+        //条件4: 按照频道实现 channelId不为空的时候就添加
+        if (dto.getChannelId() != null) {
+            // WHERE channel_id = ?
+            queryWrapper.eq(WmNews::getChannelId, dto.getChannelId());
+        }
+        //条件5:用户隔离,只查询当前登录用户的新闻
+        queryWrapper.eq(WmNews::getUserId, user.getId());
+
+        //排序:按照创建时间降序排列
+        queryWrapper.orderByDesc(WmNews::getCreatedTime);
+
+        //6、执行分页查询
+        // SELECT * FROM wm_news WHERE suer_id = ? AND ...... ORDER BY 创建时间 DESC LIMIT ?,?
+        page = page(page, queryWrapper);
+
+        //7、构建分页响应结果
+        PageResponseResult result = new PageResponseResult(
+                dto.getPage(),  // 当前页码
+                dto.getSize(),  //每页大小
+                (int) page.getTotal()//总条数
+        );
+        result.setData(page.getRecords());  //数据列表
+        return result;
+    }
+
+    /**
+     * 根据id查询文章
+     *
+     * @param id 文章id
+     * @return 文章详情
+     */
+    @Override
+    public ResponseResult findById(Integer id) {
+        if (id == null) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
+        }
+        WmUser user = WmThreadLocalUtil.getUser();
+        if (user == null) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
+        }
+        WmNews wmNews = baseMapper.selectById(id);
+        if (wmNews == null) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
+        }
+        if (!wmNews.getUserId().equals(user.getId())) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.NO_PERMISSION);
+        }
+        return ResponseResult.okResult(wmNews);
+    }
+
+    /**
+     * 发布/修改新闻 或 保存草稿
+     *
+     * @param dto 新闻提交的数据
+     * @return 操作结果
+     */
+    @Override
+    public ResponseResult submitNews(WmNewsDto dto) {
+        //1、入参校验
+        if (dto == null || StringUtils.isBlank(dto.getTitle()) || StringUtils.isBlank(dto.getContent())) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);   //参数无效
+        }
+        //2、DTO --copy--> Entity
+        WmNews wmNews = new WmNews();
+        BeanUtils.copyProperties(dto, wmNews);
+
+
+        //3、处理封面图片(无图、单图、三图)
+        // 数据库longtext是用于存储超长文本的数据类型,最大容量4GB,而varchar是用于存储较短的文本数据,最大容量是65535个字节。
+        // 所以,如果需要存储的文本数据超过了varchar的最大容量,那么就需要使用longtext数据类型。
+        if (dto.getImages() != null && dto.getImages().size() > 0) {
+            //把list里面的元素用逗号拼接,然后放到images中
+            String imageStr = StringUtils.join(dto.getImages(), ",");
+            //最后 赋值
+            wmNews.setImages(imageStr);
+        }
+
+
+        //4、处理自动封面类型
+        //如果前端选择的是自动封面,就先把type设置为null
+        //后续再根据新闻内容的图片数量进行设定
+        if (dto.getType() != null && dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)) {
+            wmNews.setType(null);
+        }
+
+
+        //5、处理文章内容--保存/修改到数据库
+        saveOrUpdateWmNews(wmNews);
+
+        //6、判断是否为草稿,是草稿,就结束流程。
+        if (dto.getStatus() != null && dto.getStatus().equals(WmNews.Status.NORMAL.getCode())) {
+            return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
+        }
+
+        //7、不是草稿,处理素材关联
+        List<String> materials = getImageUrl4Content(dto.getContent());
+        //7.1 内容图片关联素材
+        log.info("submitNews---->素材列表----->{}", materials);
+        log.info("submitNews---->素材列表长度----->{}",materials.size());
+        saveRelationalInfoForContent(materials, wmNews.getId());
+        //7.2 封面图片关联素材
+        saveRelationalInfoForCover(dto, wmNews, materials);
+
+
+        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
+    }
+
+    /**
+     * 保存封面图片和素材的关联关系(包含自动封面类型匹配)
+     * - 我们是把两个功能合并到一起
+     * |- 1、自动匹配封面类型 比如 dto.type = -1
+     * |- 2、保存封面图片和素材的关联关系
+     * 自动匹配的策略:
+     * - 内容没图片,就无图 type=0
+     * - 内容图片 < 3 张,就单图    type=1
+     * - 内容图片>= 3 张,就三图  type=3
+     *
+     * @param dto       前端传过来的新闻数据
+     * @param wmNews    已经保存的新闻实体
+     * @param materials 从内容中提取出来的图片url列表
+     */
+    private void saveRelationalInfoForCover(WmNewsDto dto, WmNews wmNews, List<String> materials) {
+        //前端传过来的封面图片列表 (可能为null)
+        List<String> images = dto.getImages();
+
+        //1、自动封装类型匹配
+        //判断 当前端设置了type= -1 的时候
+        if (dto.getType() != null && dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)) {
+            //规则1、内容图片>= 3 张,就三图  type=3
+            if (materials != null && materials.size() >= 3) {
+                //取内容中的前3张图片作为封面
+                images = materials.subList(0, 3);
+            }
+            //规则2、内容图片 < 3 张,就单图    type=1
+            else if (materials != null && materials.size() >= 1) {
+                //设置封面的类型为单图
+                wmNews.setType(WemediaConstants.WM_NEWS_SINGLE_IMAGE);
+                //取内容中的第一张图片作为封面
+                images = materials.subList(0, 1);
+            }
+            //规则3、内容没图片,就无图 type=0
+            else {
+                //设置封面的类型为无图
+                wmNews.setType(WemediaConstants.WM_NEWS_NONE_IMAGE);
+            }
+            //更新封面类型到数据库中
+            if(images !=null && images.size()>0){
+                wmNews.setImages(StringUtils.join(images, ","));
+            }
+            //更新
+            updateById(wmNews);
+        }
+
+        //2、保存封面图片和素材的关联关系
+        //如果有图片,就建立关系
+        if (images!=null && images.size()>0) {
+            //保存图片和素材关系的方法
+            saveRelationalInfo(images,wmNews.getId(),WemediaConstants.WM_COVER_REFERENCE);
+        }
+    }
+
+    @Autowired
+    private WmMaterialMapper weMaterialMapper;
+    /**
+     * 保存图片和素材关系的方法
+     * 这个方法被两个上层方法调用:
+     *  |-saveRelationalInfoForCover() 保存封面图片和素材关系的方法
+     *  |-saveRelationalInfoForContent() 保存内容图片和素材关系的方法
+     * 核心逻辑:
+     *  1、根据图片URL查询数据库中的素材记录
+     *  2、校验素材记录是否存在,关联表中有没有 素材id -> url对应的情况
+     *  3、检查素材数量是否匹配,防止重复或缺失
+     *  4、批量保存关联关系
+     * @param materials
+     * @param newId
+     * @param type
+     */
+    private void saveRelationalInfo(List<String> materials, Integer newId, Short type) {
+        //前置条件---有图片才能处理
+        if (materials ==null || materials.isEmpty()) {
+            return;
+        }
+        //1、 根据图片的url查询素材记录
+        //把图片的URL列表作为查询条件。从素材表中获取到对应的素材ID
+        List<WmMaterial> dbMaterials = weMaterialMapper.selectList(
+                new LambdaQueryWrapper<WmMaterial>()
+                        .in(WmMaterial::getUrl, materials)
+        );
+        //2、校验素材是否存在
+        if (dbMaterials == null || dbMaterials.size() == 0) {
+            //抛出异常..这个异常是属于业务异常,不是 JDK Java的异常 我们需要自定义一场
+            //throw new RuntimeException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL.getErrorMessage());
+            throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL);
+        }
+        //3、校验数量是否匹配
+        //如果传入了4个URL,但数据库只有3条,说明URL无效
+        if (materials.size() != dbMaterials.size()) {
+            //throw new RuntimeException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL.getErrorMessage());
+            throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL);
+        }
+        //4、批量保存关联关系
+        //4.1 把 从数据库里查到的 素材列表 取出所有的id,形成新的列表
+        List<Integer> idList = dbMaterials.stream()
+                .map(WmMaterial::getId)
+                .collect(Collectors.toList());
+        //5、批量保存关联关系
+        wmNewsMaterialMapper.saveRelations(idList,newId,type);
+    }
+
+    /**
+     * 保存内容图片和素材的关联关系
+     *
+     * @param materials
+     * @param id
+     */
+    private void saveRelationalInfoForContent(List<String> materials, Integer id) {
+        log.info("素材列表------>{}",materials.toString());
+        //内容图片 type = 0,
+        saveRelationalInfo(materials,id,WemediaConstants.WM_CONTENT_REFERENCE);
+    }
+
+    /**
+     * 从新闻内容中获取素材url列表的方法
+     * 新闻content格式就是一组JSON字符串
+     *  [
+     *   {"type": "text","value": "...."},
+     *   {"type": "image", "value": "http://192.168.200.129:.....2fb.jpg"},
+     *   {"type": "image", "value": "http://192.168.32.128:9000/....c164.jpg"}
+     * ]
+     *  我们要提取的结果是:
+     *  ["http://192.168.200.129:.....2fb.jpg","http://192.168.32.128:9000/....c164.jpg"]
+     *
+     * @param content   新闻内容的JSON字符串
+     * @return  图片的URL列表,如果没有图片就返回null
+     */
+    private List<String> getImageUrl4Content(String content) {
+        List<String> materials = new ArrayList<>();
+
+        //判空,如果是空的,就直接返回
+        if (StringUtils.isBlank(content)) {
+            return materials;
+        }
+
+        try{
+            //1、将JSON字符串解析为List<Map>
+            List<Map> maps = JSON.parseArray(content, Map.class);
+
+            //2、遍历每一项
+            for (Map map : maps) {
+                //3、判断是否是图片
+                if (WemediaConstants.WM_NEWS_TYPE_IMAGE.equals(map.get("type"))) {
+                    //提取图片的url
+                    String imgUrl = (String)map.get("value");
+                    //添加到图片列表中
+                    materials.add(imgUrl);
+                }
+            }
+        }catch (Exception e){
+            //JSON解析的时候可能会因为格式不对,触发异常
+            log.error("getImageUrl4Content error:{}",e.getMessage());
+        }
+
+        return materials;
+    }
+
+
+    @Autowired
+    private WmNewsMaterialMapper wmNewsMaterialMapper;
+
+
+    /**
+     * 保存或修改文章
+     * 新增和修改新闻的统一处理
+     * 主要的参数条件就是看id是否为空:
+     * id==null    新增
+     * id!=null    修改
+     * |-如果是修改,那么就要先清除关联,因为后面要重新建立图片(封面/内容)和素材的关联关系,如果旧的不删除,就会重复
+     *
+     * @param wmNews
+     */
+    private void saveOrUpdateWmNews(WmNews wmNews) {
+        //1、先补全新闻需要的基本属性
+        //1.1、设置当前的登录用户
+        Integer userId = WmThreadLocalUtil.getUser().getId();
+        wmNews.setUserId(userId);
+        //1.2、设置创建时间
+        Date date = new Date();
+        wmNews.setCreatedTime(date);
+        //1.3、设置提交时间 和创建时间相同,表示本次操作的时间
+        wmNews.setSubmitedTime(date);
+        //1.4、设置状态
+        wmNews.setEnable((short) 1);
+
+        //2、判断是新增还是修改
+        if (wmNews.getId() == null) {
+            // 新增
+            //save方法是Mybatis-Plus提供的
+            //Mybatis-Plus在新增之后还会自动回填id到wmNews.getId()
+            save(wmNews);
+        } else {
+            //修改
+            //2.1、先清除关联关系
+            wmNewsMaterialMapper.delete(
+                    new LambdaQueryWrapper<WmNewsMaterial>()    //创建LambdaQueryWrapper对象
+                            .eq(WmNewsMaterial::getNewsId, wmNews.getId())   //封装删除条件 newsId = wmNews.getId()
+            );
+
+            //2.2、再保存新的关联关系
+            updateById(wmNews);
+        }
+    }
+}

+ 59 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/java/com/zhaozhaonews/wemedia/service/impl/WmUserServiceImpl.java

@@ -0,0 +1,59 @@
+package com.zhaozhaonews.wemedia.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhaozhaonews.model.common.dto.ResponseResult;
+import com.zhaozhaonews.model.common.enums.AppHttpCodeEnum;
+import com.zhaozhaonews.model.wemedia.dto.WmLoginDto;
+import com.zhaozhaonews.model.wemedia.entity.WmUser;
+import com.zhaozhaonews.util.common.AppJwtUtil;
+import com.zhaozhaonews.wemedia.mapper.WmUserMapper;
+import com.zhaozhaonews.wemedia.service.WmUserService;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.util.DigestUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title WmUserServiceImpl
+ * @description
+ * @create 2026/4/26
+ */
+@Service
+public class WmUserServiceImpl extends ServiceImpl<WmUserMapper, WmUser> implements WmUserService {
+
+    @Override
+    public ResponseResult login(WmLoginDto dto) {
+        //1.检查参数
+        if(StringUtils.isBlank(dto.getName()) || StringUtils.isBlank(dto.getPassword())){
+            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"用户名或密码为空");
+        }
+
+        //2.查询用户
+        WmUser wmUser = getOne(Wrappers.<WmUser>lambdaQuery().eq(WmUser::getName, dto.getName()));
+        if(wmUser == null){
+            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
+        }
+
+        //3.比对密码
+        String salt = wmUser.getSalt(); //获取盐
+        String pswd = dto.getPassword();    //获取密码
+        pswd = DigestUtils.md5DigestAsHex((pswd + salt).getBytes());
+        if(pswd.equals(wmUser.getPassword())){
+            //4.返回数据  jwt
+            Map<String,Object> map  = new HashMap<>();
+            map.put("token", AppJwtUtil.getToken(wmUser.getId().longValue()));  //封装token
+            wmUser.setSalt(""); //把盐清空
+            wmUser.setPassword(""); //把密码清空
+            map.put("user",wmUser); //封装用户信息
+            return ResponseResult.okResult(map);
+
+        }else {
+            return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
+        }
+    }
+}

+ 12 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/resources/bootstrap.yml

@@ -0,0 +1,12 @@
+server:
+  port: 51803
+spring:
+  application:
+    name: zhaozhaonews-wemedia
+  cloud:
+    nacos:
+      discovery:
+        server-addr: 192.168.200.129:8848
+      config:
+        server-addr: 192.168.200.129:8848
+        file-extension: yaml

+ 95 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/resources/logback.xml

@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration debug="false" scan="true" scanPeriod="30 seconds">
+    <!-- ======================== 日志全局配置 ======================== -->
+    <!-- 日志文件保存根目录:项目根目录下的 logs 文件夹(跨平台兼容) -->
+    <property name="LOG_HOME" value="${user.dir}/logs"/>
+
+    <!-- ======================== 控制台输出(开发用) ======================== -->
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <!-- 日志输出格式编码器 -->
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <!-- 优化格式:增加颜色显示,更易读 -->
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}) - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- ======================== 文件输出(生产用) ======================== -->
+    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 当前日志文件路径 -->
+        <file>${LOG_HOME}/zhaozhaonews-wemedia.log</file>
+        <!-- 滚动策略:同时支持 按天归档 + 按大小切割 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <!-- 日志文件命名规则:日期+序号 -->
+            <FileNamePattern>${LOG_HOME}/zhaozhaonews-wemedia.log.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
+            <!-- 单个日志文件最大大小:超过自动切割 -->
+            <MaxFileSize>10MB</MaxFileSize>
+            <!-- 日志文件最多保留天数 -->
+            <MaxHistory>30</MaxHistory>
+            <!-- 所有日志文件总大小限制 -->
+            <totalSizeCap>1GB</totalSizeCap>
+        </rollingPolicy>
+        <!-- 文件日志输出格式 -->
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- ======================== 错误日志单独输出 ======================== -->
+    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_HOME}/error.log</file>
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>ERROR</level>
+        </filter>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <FileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.log</FileNamePattern>
+            <MaxHistory>30</MaxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- ======================== 日志级别配置 ======================== -->
+
+    <!-- 1. 项目代码日志:DEBUG级别,便于调试 -->
+    <logger name="com.interview" level="DEBUG" additivity="false">
+        <appender-ref ref="STDOUT"/>
+        <appender-ref ref="FILE"/>
+    </logger>
+
+    <!-- 2. MyBatis-Plus SQL日志(关键修正!) -->
+    <logger name="com.baomidou.mybatisplus" level="DEBUG"/>
+
+    <!-- 3. Spring框架日志:INFO级别,减少噪音 -->
+    <logger name="org.springframework" level="INFO"/>
+
+    <!-- 4. Spring Web日志:DEBUG级别,便于查看请求响应 -->
+    <logger name="org.springframework.web" level="INFO"/>
+
+    <!-- 5. Knife4j/Swagger日志:减少噪音 -->
+    <logger name="springfox" level="WARN"/>
+    <logger name="io.swagger" level="WARN"/>
+
+    <!-- 6. 数据库连接池日志 -->
+    <logger name="com.zaxxer.hikari" level="INFO"/>
+
+    <!-- 7. JDBC SQL日志(查看原始SQL) -->
+    <logger name="java.sql.Connection" level="DEBUG"/>
+    <logger name="java.sql.Statement" level="DEBUG"/>
+    <logger name="java.sql.PreparedStatement" level="DEBUG"/>
+    <logger name="java.sql.ResultSet" level="DEBUG"/>
+
+    <!-- ======================== 根日志配置 ======================== -->
+    <!-- 全局默认日志级别:INFO -->
+    <root level="INFO">
+        <!-- 输出到控制台 -->
+        <appender-ref ref="STDOUT"/>
+        <!-- 输出到文件 -->
+        <appender-ref ref="FILE"/>
+        <!-- 错误日志单独输出 -->
+        <appender-ref ref="ERROR_FILE"/>
+    </root>
+</configuration>

+ 13 - 0
zhaozhaonews/zhaozhaonews-service/zhaozhaonews-wemedia/src/main/resources/mapper/WmNewsMaterialMapper.xml

@@ -0,0 +1,13 @@
+<?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.zhaozhaonews.wemedia.mapper.WmNewsMaterialMapper">
+    <!--批量插入 新闻-素材 关联表-->
+    <insert id="saveRelations">
+        INSERT INTO zhaozhaonews_wemedia.wm_news_material (material_id,news_id,type,ord)
+        VALUES
+        <foreach collection="materialIds" index="ord" item="mid" separator=",">
+            (#{mid},#{newsId},#{type},#{ord})
+        </foreach>
+    </insert>
+</mapper>