1 完善实名认证功能
1.1 实名认证分布式事务问题
1.1.1 问题分析
在昨天的实名认证代码中,审核完毕后添加 id==5的演示异常,重新使用postman进行测试, 会发现 出现异常后 本地方法因为有
@Transactional注解 对ap_user ap_user_realname的操作会回滚
而 基于Feign远程调用 article服务 wemedia服务 确没有回滚
这时我们的代码 存在分布式事务问题,传统的数据库事务无法解决
1.1.2 seata快速回顾
Seata是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。
官网地址:http://seata.io/,其中的文档、播客中提供了大量的使用说明、源码分析。
Seata事务管理中有三个重要的角色:
-
TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
-
TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
-
RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
整体的架构如图:
Seata基于上述架构提供了四种不同的分布式事务解决方案:
-
XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
-
TCC模式:最终一致的分阶段事务模式,有业务侵入
-
AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
-
SAGA模式:长事务模式,有业务侵入
无论哪种方案,都离不开TC,也就是事务的协调者。
Seata AT模式
阶段一RM的工作:
- 注册分支事务
- 记录undo-log(数据快照)
- 执行业务sql并提交
- 报告事务状态
阶段二提交时RM的工作:
- 删除undo-log即可
阶段二回滚时RM的工作:
- 根据undo-log恢复数据到更新前
1.1.3 项目集成seata 记得Springboot seata用1.6.1版本,低版本会报错
准备seata数据库
导入资料中的seata.sql
docker部署seata TC
如果使用docker安装 registry 默认使用 file 模式 存储事务数据 :
docker run --name seata --restart=always -p 8091:8091 -e SEATA_IP=192.168.200.130 -e SEATA_PORT=8091 -v seata-config:/seata-server/resources -id seataio/seata-server:1.4.2
我们使用nacos作为seata的配置和注册中心,方便以后的高可用 及 统一的配置管理
在seata命名空间 创建配置seataServer.properties
分组为: SEATA_GROUP
1.1.3 项目集成seata
准备seata数据库
导入资料中的seata.sql
docker部署seata TC
如果使用docker安装 registry 默认使用 file 模式 存储事务数据 :
docker run --name seata --restart=always -p 8091:8091 -e SEATA_IP=192.168.200.130 -e SEATA_PORT=8091 -v seata-config:/seata-server/resources -id seataio/seata-server:1.4.2
我们使用nacos作为seata的配置和注册中心,方便以后的高可用 及 统一的配置管理
在seata命名空间 创建配置seataServer.properties
分组为: SEATA_GROUP
# 数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://192.168.200.130:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
# 事务、日志等配置
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
# 客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
# 关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
修改seataTC配置
准备好nacos配置中心后,修改seata TC端 使用nacos作为配置中心
# 进入到挂载目录 cd /var/lib/docker/volumes/seata-config/_data # 修改注册中心配置 vi registry.conf
配置中内容:
registry { # tc服务的注册中心类,这里选择nacos,也可以是eureka、zookeeper等 type = "nacos" nacos { # seata tc 服务注册到 nacos的服务名称,可以自定义 spring.application.name application = "seata-tc-server" serverAddr = "192.168.200.130:8848" group = "SEATA_GROUP" namespace = "seata" cluster = "SH" username = "nacos" password = "nacos" } } config { # 读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置 type = "nacos" # 配置nacos地址等信息 nacos { serverAddr = "192.168.200.130:8848" namespace = "seata" group = "SEATA_GROUP" username = "nacos" password = "nacos" dataId = "seataServer.properties" } }
修改后 记得重启seata
注意检查 mysql中是否准备了seata库哦~~
微服务配置seata
配置步骤参考官网
创建日志表undo_log (已创建)
分别在leadnews_user、leadnews_article、leadnews_wemedia三个库中都创建undo_log表
创建seata共享配置
在配置中心nacos 的 dev
环境中 创建share-seata.yml
seata: registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址 # 参考tc服务自己的registry.conf中的配置 type: nacos nacos: # tc server-addr: ${spring.profiles.ip}:8848 namespace: "seata" group: SEATA_GROUP application: seata-tc-server # tc服务在nacos中的服务名称 tx-service-group: seata-demo # 事务组,根据这个获取tc服务的cluster名称 service: vgroup-mapping: # 事务组与TC服务cluster的映射关系 seata-demo: SH
修改微服务
参与分布式事务的微服务 ( leadnews-user、leadnews-wemedia、leadnews-article),引入依赖
<dependencies> <!--seata--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <!--版本较低,1.3.0,因此排除--> <exclusion> <artifactId>seata-spring-boot-starter</artifactId> <groupId>io.seata</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <!--seata starter 采用1.4.2版本--> <version>${seata.version}</version> </dependency> </dependencies>
修改bootstrap.yml
配置
以 leadnews-user服务为例 其它一样
spring: application: name: leadnews-user # 服务名称 profiles: active: dev # 开发环境配置 ip: 192.168.200.130 # 环境ip地址 cloud: nacos: discovery: # 注册中心地址配置 server-addr: ${spring.profiles.ip}:8848 namespace: ${spring.profiles.active} config: # 配置中心地址配置 server-addr: ${spring.profiles.ip}:8848 namespace: ${spring.profiles.active} file-extension: yml # data-id 后缀 name: ${spring.application.name} # data-id名称 shared-configs: # 共享配置 - data-id: share-feign.yml # 配置文件名-Data Id group: DEFAULT_GROUP # 默认为DEFAULT_GROUP refresh: false # 是否动态刷新,默认为false - data-id: share-seata.yml # 配置文件名-Data Id group: DEFAULT_GROUP # 默认为DEFAULT_GROUP refresh: false # 是否动态刷新,默认为fals
管理事务加全局事务注解
在实名认证审核的方法上,加上seata提供的全局事务管理注解 @GlobalTransactional
注解, 开启全局事务
测试分布式事务
再次通过postman测试使用认证审核接口,查看出现异常时 事务是否回滚
注意字段为tinyind的数据长度不要设为1,不然大于0都默认值为true就不会回滚了
1.2 扩展 - 网关+knife4j实现聚合文档
见扩展资料
1.3 扩展 - AI实名认证方案
见扩展资料
2 云存储解决方案-阿里云OSS
2.1 阿里云OSS简介
2.4 OSS starter工具封装
(1)heima-leadnews-basic
模块,在当前模块下创建 heima-file-spring-boot-starter
子模块,
添加依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <!--OSS--> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.10.2</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>21.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>
(2)创建包com.heima.file
资料文件夹下导入 service
和 config
包下的内容:
结构如下:
(3)在resources
目录下新建 META-INF/spring.factories
配置文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.heima.file.service.impl.OSSAliyunFileStorageService
(4)wemedia-service
添加OSS配置
wemedia-service
添加依赖
<dependency> <artifactId>heima-file-spring-boot-starter</artifactId> <groupId>com.heima</groupId> <version>1.0-SNAPSHOT</version> </dependency>
配置中心nacos添加 share-file.yml
共享配置
#OSS配置 file: oss: bucket-name: <替换成自己的> access-key-id: <替换成自己的> access-key-secret: <替换成自己的> endpoint: oss-cn-shanghai.aliyuncs.com web-site: <替换成自己的> proxy-username: aliyun-sdk-java socket-timeout: 10000 idle-connection-time: 10000 connection-request-timeout: 4000 max-connections: 2048 max-error-retry: 5 white-list: 127.0.0.1 connection-timeout: 10000 prefix: material
bucket就是我们刚刚申请的,在它的概览页面 有对应的外网访问地址前缀,贴到配置中 记得加上http://开头 和 斜杠结尾哦
bootstrap.yml
新增共享配置
spring: cloud: nacos: config: # 配置中心地址配置 shared-configs: # 共享配置 - data-id: share-file.yml # 配置文件名-Data Id group: DEFAULT_GROUP # 默认为DEFAULT_GROUP refresh: false # 是否动态刷新,默认为false
(5)在 wemedia-service
中新建测试类测试OSS工具类
package com.heima.wemedia; import com.heima.file.service.FileStorageService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; /** * @Description: * @Version: V1.0 */ @SpringBootTest(classes = WemediaApplication.class) @RunWith(SpringRunner.class) public class OssTest { @Autowired FileStorageService fileStorageService; @Value("${file.oss.web-site}") String webSite; @Test public void testFileUpload() throws Exception { // FileInputStream inputStream = new FileInputStream(new File("/Users/Ares/Desktop/temp/banner1.jpg")); // String wemedia = fileStorageService.store("upload", "aaa1.jpg", inputStream); // System.out.println(webSite+wemedia); // 删除文件 fileStorageService.delete("wemedia/2020/12/20201227/aaa1.jpg"); } }
3 自媒体管理
3.1 自媒体用户登录
自媒体登录操作与admin端登录思路是一样的
(1)自媒体登录接口定义
接口地址:/login/in
请求方式:POST
请求数据类型:application/json
响应数据类型:*/*
接口描述:
请求示例:
{ "name": "", "password": "" }
请求参数:
参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema |
---|---|---|---|---|---|
dto | dto | body | true | WmUserDto | WmUserDto |
name | false | string | |||
password | false | string |
WmUserDto
@Data public class WmUserDTO { /** * 用户名 */ private String name; /** * 密码 */ private String password; }
WmUserVO
@Data public class WmUserVO { private Integer id; private String name; private String nickname; private String image; private String email; private Date loginTime; private Date createdTime; }
(1)业务层service
新增业务层接口
package com.heima.wemedia.service; import com.baomidou.mybatisplus.extension.service.IService; import com.heima.model.common.dtos.ResponseResult; import com.heima.model.wemedia.dtos.WmUserDTO; import com.heima.model.wemedia.pojos.WmUser; public interface WmUserService extends IService<WmUser> { /** * 登录 * @param dto * @return */ public ResponseResult login(WmUserDTO dto); }
实现类:
package com.heima.wemedia.service.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.heima.common.exception.CustException; import com.heima.model.common.dtos.ResponseResult; import com.heima.model.common.enums.AppHttpCodeEnum; import com.heima.model.wemedia.dtos.WmUserDTO; import com.heima.model.wemedia.pojos.WmUser; import com.heima.model.wemedia.vos.WmUserVO; import com.heima.utils.common.AppJwtUtil; import com.heima.wemedia.mapper.WmUserMapper; import com.heima.wemedia.service.WmUserService; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.util.DigestUtils; import java.util.HashMap; import java.util.Map; @Service public class WmUserServiceImpl extends ServiceImpl<WmUserMapper, WmUser> implements WmUserService { /** * 登录 * @param dto * @return */ @Override public ResponseResult login(WmUserDTO dto) { //1.检查参数 if(StringUtils.isBlank(dto.getName())||StringUtils.isBlank(dto.getPassword())){ CustException.cust(AppHttpCodeEnum.PARAM_INVALID); } //2 查询自媒体用户 WmUser wmUser = getOne(Wrappers.<WmUser>lambdaQuery() .eq(WmUser::getName, dto.getName())); if (wmUser == null) { CustException.cust(AppHttpCodeEnum.DATA_NOT_EXIST); } if(wmUser.getStatus().intValue()!=9){// 可替换为常量 CustException.cust(AppHttpCodeEnum.DATA_NOT_ALLOW,"该用户状态异常,请联系管理员"); } String pswd = DigestUtils.md5DigestAsHex((dto.getPassword() + wmUser.getSalt()).getBytes()); if (!wmUser.getPassword().equals(pswd)) { CustException.cust(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR); } //4 返回jwt结果 Map<String, Object> map = new HashMap<>(); map.put("token", AppJwtUtil.getToken(Long.valueOf(wmUser.getId()))); WmUserVO userVO = new WmUserVO(); BeanUtils.copyProperties(wmUser,userVO); map.put("user", userVO); return ResponseResult.okResult(map); } }
(2)控制器controller
新增控制器
package com.heima.wemedia.controller.v1; import com.heima.model.common.dtos.ResponseResult; import com.heima.model.wemedia.dtos.WmUserDTO; import com.heima.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 WmUserDTO dto){ return wmUserService.login(dto); } }
(3)小刀无网关测试
3.2 自媒体网关和获取登录用户
(1)自媒体网关
上一次创建的admin平台管理的网关,自媒体端也有自己的网关,后边还需要创建app端的网关
自媒体网关与admin端网关几乎是一样的,可以参考admin端网关
-
创建
wemedia-gateway
网关工程:pom文件如下
-
从admin网关中把必要的类拷贝过来
-
新增
bootstrap.yml
spring:
application:
name: wemedia-gateway # 服务名称
profiles:
active: dev # 开发环境配置
ip: 192.168.200.130 # 环境ip地址
cloud:
nacos:
discovery: # 注册中心地址配置
server-addr: ${spring.profiles.ip}:8848
namespace: ${spring.profiles.active}
config: # 配置中心地址配置
server-addr: ${spring.profiles.ip}:8848
namespace: ${spring.profiles.active}
file-extension: yml # data-id 后缀
name: ${spring.application.name} # data-id名称
配置中心dev环境新增 wemedia-gateway.yml
server: port: 6002 spring: cloud: gateway: globalcors: cors-configurations: '[/**]': # 匹配所有请求 allowedOrigins: "*" #跨域处理 允许所有的域 allowedMethods: # 支持的方法 - GET - POST - PUT - DELETE routes: # 平台管理 - id: wemedia uri: lb://leadnews-wemedia predicates: - Path=/wemedia/** filters: - StripPrefix= 1
(2)获取登录用户思路分析
在去保存图片之前,需要先知道是哪一位自媒体人登录了当前系统,也就是需要确定哪个自媒体人上传了图片
操作步骤:
-
上传图片需要携带token
-
首先请求到网关服务,解析token是否有效,如果有效,解析后把用户数据设置到下一级请求的header中
-
在自媒体微服务中使用过滤器解析header中的数据,拿到用户数据,使用threadlocal设置到当前线程中
-
在具体业务代码中可以从当前线程threadlocal中获取用户
(3)获取登录用户
前面两步都已经实现,主要是在代码中实现后两步即可
1)添加工具类
在使用过滤器解析后的用户需要放在当前线程中,可以使用工具类来实现,
在heima-leadnews-model
中com.heima.model.threadlocal
添加如下类,用于存储自媒体的用户登录信息
package com.heima.model.threadlocal; import com.heima.model.wemedia.pojos.WmUser; public class WmThreadLocalUtils { private final static ThreadLocal<WmUser> userThreadLocal = new ThreadLocal<>(); /** * 设置当前线程中的用户 * @param user */ public static void setUser(WmUser user){ userThreadLocal.set(user); } /** * 获取线程中的用户 * @return */ public static WmUser getUser( ){ return userThreadLocal.get(); } /** * 清空线程中的用户信息 */ public static void clear(){ userThreadLocal.remove(); } }
2)在wemedia-service
自媒体微服务中使用过滤器解析header数据并设置到当前线程中
package com.heima.wemedia.filter; import com.heima.model.threadlocal.WmThreadLocalUtils; import com.heima.model.wemedia.pojos.WmUser; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.filter.GenericFilterBean; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Order(1) @WebFilter(filterName = "wmTokenFilter",urlPatterns = "/*") @Slf4j @Component // 扫描包 public class WmTokenFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; //得到header中的信息 String userId = request.getHeader("userId"); if(userId != null){ WmUser wmUser = new WmUser(); wmUser.setId(Integer.valueOf(userId)); // 保存到当前线程中 WmThreadLocalUtils.setUser(wmUser); } // 如果没有则直接放行 filterChain.doFilter(request,response); // 过滤器处理完毕后 清空用户信息 WmThreadLocalUtils.clear(); } }
3.3 素材管理
效果演示: heima-leadnews-wemedia
3.3.1 图片上传
3.3.1.1 文件上传需求
对应实体类:
package com.heima.model.wemedia.pojos; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serializable; import java.util.Date; /** * <p> * 自媒体图文素材信息表 * </p> * * @author itheima */ @Data @TableName("wm_material") public class WmMaterial implements Serializable { private static final long serialVersionUID = 1L; /** * 主键 */ @TableId(value = "id", type = IdType.AUTO) private Integer id; /** * 自媒体用户ID */ @TableField("user_id") private Integer userId; /** * 图片地址 */ @TableField("url") private String url; /** * 素材类型 0 图片 1 视频 */ @TableField("type") private Short type; /** * 是否收藏 */ @TableField("is_collection") private Short isCollection; /** * 创建时间 */ @TableField("created_time") private Date createdTime; }
3.3.1.2 接口定义
接口地址:/api/v1/material/upload_picture
请求方式:POST
请求数据类型:multipart/form-data
响应数据类型:*/*
接口描述: 文件上传
请求参数:
参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema |
---|---|---|---|---|---|
multipartFile | multipartFile | formData | false | file |
3.3.1.3 定义mapper
新建接口:com.heima.wemedia.mapper.WmMaterialMapper
package com.heima.wemedia.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.heima.model.wemedia.pojos.WmMaterial; import org.apache.ibatis.annotations.Mapper; public interface WmMaterialMapper extends BaseMapper<WmMaterial> { }
3.3.1.4 定义service
新建接口:com.heima.wemedia.service.MaterialService
package com.heima.wemedia.service; import com.baomidou.mybatisplus.extension.service.IService; import com.heima.model.common.dtos.ResponseResult; import com.heima.model.wemedia.pojos.WmMaterial; import org.springframework.web.multipart.MultipartFile; public interface WmMaterialService extends IService<WmMaterial> { /** * 上传图片接口 * @param multipartFile * @return */ ResponseResult uploadPicture(MultipartFile multipartFile); }
实现类:
package com.heima.wemedia.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.heima.common.exception.CustException; import com.heima.common.exception.CustomException; import com.heima.file.service.FileStorageService; import com.heima.model.common.dtos.ResponseResult; import com.heima.model.common.enums.AppHttpCodeEnum; import com.heima.model.threadlocal.WmThreadLocalUtils; import com.heima.model.wemedia.pojos.WmMaterial; import com.heima.model.wemedia.pojos.WmUser; import com.heima.wemedia.mapper.WmMaterialMapper; import com.heima.wemedia.service.WmMaterialService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.UUID; @Service @Slf4j public class WmMaterialServiceImpl extends ServiceImpl<WmMaterialMapper, WmMaterial> implements WmMaterialService { @Autowired FileStorageService fileStorageService; @Value("${file.oss.prefix}") String prefix; @Value("${file.oss.web-site}") String webSite; @Override public ResponseResult<WmMaterial> uploadPicture(MultipartFile multipartFile) { // 1 参数校验 if (multipartFile == null || multipartFile.getSize() == 0) { CustException.cust(AppHttpCodeEnum.PARAM_INVALID,"请上传正确的文件"); } // 当前线程中获取用户ID WmUser user = WmThreadLocalUtils.getUser(); if (user == null) { CustException.cust(AppHttpCodeEnum.NO_OPERATOR_AUTH); } String originalFilename = multipartFile.getOriginalFilename(); if (!checkFileSuffix(originalFilename)) { CustException.cust(AppHttpCodeEnum.PARAM_INVALID,"请上传正确的素材格式,[jpg,jpeg,png,gif]"); } // 2 上传到oss String fileId = null; try { String filename = UUID.randomUUID().toString().replace("-", ""); String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); fileId = fileStorageService.store(prefix, filename+suffix, multipartFile.getInputStream()); log.info("阿里云OSS 文件 fileId: {}",fileId); } catch (IOException e) { e.printStackTrace(); log.error("阿里云文件上传失败 uploadPicture error: {}", e); CustException.cust(AppHttpCodeEnum.SERVER_ERROR,"服务器繁忙请稍后重试"); } // 3 封装数据并保持到素材库中 WmMaterial wmMaterial = new WmMaterial(); wmMaterial.setIsCollection((short)0); wmMaterial.setType((short)0); wmMaterial.setCreatedTime(new Date()); // 设置文件id wmMaterial.setUrl(fileId); wmMaterial.setUserId(user.getId()); save(wmMaterial); // 前端显示 wmMaterial.setUrl(webSite+fileId); // 4 返回结果 return ResponseResult.okResult(wmMaterial); } /** * 检查文件格式 目前仅仅支持jpg jpeg png gif 图片的上传 * @param path * @return */ private boolean checkFileSuffix(String path){ if(StringUtils.isBlank(path)) return false; List<String> allowSuffix = Arrays.asList("jpg", "jpeg", "png", "gif"); boolean isAllow = false; for (String suffix : allowSuffix) { if(path.endsWith(suffix)){ isAllow = true; break; } } return isAllow; } }
3.3.1.5 控制层
package com.heima.wemedia.controller.v1; import com.heima.model.common.dtos.ResponseResult; import com.heima.wemedia.service.WmMaterialService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @Api(value = "素材管理API",tags = "素材管理API") @RestController @RequestMapping("/api/v1/material") public class WmMaterialController { @Autowired private WmMaterialService materialService; @ApiOperation("上传素材") @PostMapping("/upload_picture") public ResponseResult uploadPicture(MultipartFile multipartFile) { return materialService.uploadPicture(multipartFile); } }
3.3.1.6 测试
启动清单:
-
naocs
-
WemediaGatewayApplication 网关
-
WemediaApplication 自媒体服务
使用postman 或 小刀文档测试,需要先登录设置token后再操作
3.3.2 素材列表加载
-
素材列表查询,前台有可能给传一个条件 是否收藏 0 不收藏 1 收藏
-
只能查询当前登录用户上传的素材
-
按照发布时间 降序排序
-
返回的结果 , 图片需要加上前缀访问路径
3.3.2.1 接口定义
接口地址:/api/v1/material/list
请求方式:POST
请求数据类型:application/json
响应数据类型:*/*
接口描述: 查询素材列表
请求示例:
{ "isCollected": 0, "page": 0, "size": 0 }
请求参数:
参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema |
---|---|---|---|---|---|
dto | dto | body | true | WmMaterialDto | WmMaterialDto |
isCollected | false | integer(int32) | |||
page | 当前第几页 | false | integer(int32) | ||
size | 每页显示条数 | false | integer(int32) |
WmMaterialDTO
package com.heima.model.wemedia.dtos; import com.heima.model.common.dtos.PageRequestDTO; import lombok.Data; @Data public class WmMaterialDTO extends PageRequestDTO { Short isCollection; //1 查询收藏的 0: 未收藏 }
3.3.2.2 定义service
在com.heima.wemedia.service.WmMaterialService
类中新增方法
/** * 素材列表查询 * @param dto * @return */ ResponseResult findList(WmMaterialDTO dto);
实现类:
@Override public ResponseResult findList(WmMaterialDTO dto) { // 1 参数校验 dto.checkParam(); // 2 执行业务查询 LambdaQueryWrapper<WmMaterial> wrapper = new LambdaQueryWrapper<>(); // 收藏 if (dto.getIsCollection() != null && dto.getIsCollection() == 1) { wrapper.eq(WmMaterial::getIsCollection, dto.getIsCollection()); } // 当前登录用户的素材 WmUser user = WmThreadLocalUtils.getUser(); if (user == null) { throw new CustomException(AppHttpCodeEnum.NO_OPERATOR_AUTH); } wrapper.eq(WmMaterial::getUserId, user.getId()); // 时间倒序 wrapper.orderByDesc(WmMaterial::getCreatedTime); IPage<WmMaterial> pageParam = new Page<>(dto.getPage(), dto.getSize()); IPage<WmMaterial> resultPage = page(pageParam, wrapper); List<WmMaterial> records = resultPage.getRecords(); for (WmMaterial record : records) { record.setUrl(webSite + record.getUrl()); } // 3 封装结果 PageResponseResult pageResponseResult = new PageResponseResult(dto.getPage(), dto.getSize(), resultPage.getTotal()); pageResponseResult.setData(records); return pageResponseResult; }
3.3.2.3 控制层
在com.heima.wemedia.controller.v1.WmMaterialController
新增方法
@ApiOperation("查询素材列表") @PostMapping("/list") public ResponseResult findList(@RequestBody WmMaterialDTO dto) { return materialService.findList(dto); }
3.3.2.4 测试
使用postman测试,需要先登录设置token后再操作