当前位置: 首页 > news >正文

java 黑马头条 day3 实名认证分布式事务问题 seata

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.nameapplication = "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: nacosnacos: # tcserver-addr: ${spring.profiles.ip}:8848namespace: "seata"group: SEATA_GROUPapplication: 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}:8848namespace: ${spring.profiles.active}config: # 配置中心地址配置server-addr: ${spring.profiles.ip}:8848namespace: ${spring.profiles.active}file-extension: yml # data-id 后缀name: ${spring.application.name} # data-id名称shared-configs: # 共享配置- data-id: share-feign.yml # 配置文件名-Data Idgroup: DEFAULT_GROUP   # 默认为DEFAULT_GROUPrefresh: false   # 是否动态刷新,默认为false- data-id: share-seata.yml # 配置文件名-Data Idgroup: DEFAULT_GROUP   # 默认为DEFAULT_GROUPrefresh: 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

资料文件夹下导入 serviceconfig包下的内容:

结构如下:

(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.comweb-site: <替换成自己的>proxy-username: aliyun-sdk-javasocket-timeout: 10000idle-connection-time: 10000connection-request-timeout: 4000max-connections: 2048max-error-retry: 5white-list: 127.0.0.1connection-timeout: 10000prefix: material

bucket就是我们刚刚申请的,在它的概览页面 有对应的外网访问地址前缀,贴到配置中 记得加上http://开头 和 斜杠结尾哦

bootstrap.yml 新增共享配置

spring:cloud:nacos:config: # 配置中心地址配置shared-configs: # 共享配置- data-id: share-file.yml # 配置文件名-Data Idgroup: DEFAULT_GROUP   # 默认为DEFAULT_GROUPrefresh: 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 {
​@AutowiredFileStorageService fileStorageService;@Value("${file.oss.web-site}")String webSite;
​@Testpublic 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
dtodtobodytrueWmUserDtoWmUserDto
  namefalsestring
  passwordfalsestring

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*/@Overridepublic 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 {@Autowiredprivate 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- DELETEroutes:# 平台管理- id: wemediauri: lb://leadnews-wemediapredicates:- Path=/wemedia/**filters:- StripPrefix= 1

(2)获取登录用户思路分析

在去保存图片之前,需要先知道是哪一位自媒体人登录了当前系统,也就是需要确定哪个自媒体人上传了图片

 

操作步骤:

  1. 上传图片需要携带token

  2. 首先请求到网关服务,解析token是否有效,如果有效,解析后把用户数据设置到下一级请求的header中

  3. 在自媒体微服务中使用过滤器解析header中的数据,拿到用户数据,使用threadlocal设置到当前线程中

  4. 在具体业务代码中可以从当前线程threadlocal中获取用户

(3)获取登录用户

前面两步都已经实现,主要是在代码中实现后两步即可

1)添加工具类

在使用过滤器解析后的用户需要放在当前线程中,可以使用工具类来实现,

heima-leadnews-modelcom.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 {@Overridepublic 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
multipartFilemultipartFileformDatafalsefile

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 {@AutowiredFileStorageService fileStorageService;@Value("${file.oss.prefix}")String prefix;@Value("${file.oss.web-site}")String webSite;@Overridepublic ResponseResult<WmMaterial> uploadPicture(MultipartFile multipartFile) {// 1 参数校验if (multipartFile == null || multipartFile.getSize() == 0) {CustException.cust(AppHttpCodeEnum.PARAM_INVALID,"请上传正确的文件");}// 当前线程中获取用户IDWmUser 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 上传到ossString 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());// 设置文件idwmMaterial.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 {@Autowiredprivate WmMaterialService materialService;@ApiOperation("上传素材")@PostMapping("/upload_picture")public ResponseResult uploadPicture(MultipartFile multipartFile) {return materialService.uploadPicture(multipartFile);}
}

3.3.1.6 测试

启动清单:

  1. naocs

  2. WemediaGatewayApplication 网关

  3. WemediaApplication 自媒体服务

使用postman 或 小刀文档测试,需要先登录设置token后再操作

3.3.2 素材列表加载

  1. 素材列表查询,前台有可能给传一个条件 是否收藏 0 不收藏 1 收藏

  2. 只能查询当前登录用户上传的素材

  3. 按照发布时间 降序排序

  4. 返回的结果 , 图片需要加上前缀访问路径

3.3.2.1 接口定义

接口地址:/api/v1/material/list

请求方式:POST

请求数据类型:application/json

响应数据类型:*/*

接口描述: 查询素材列表

请求示例:

{"isCollected": 0,"page": 0,"size": 0
}

请求参数:

参数名称参数说明in是否必须数据类型schema
dtodtobodytrueWmMaterialDtoWmMaterialDto
  isCollectedfalseinteger(int32)
  page当前第几页falseinteger(int32)
  size每页显示条数falseinteger(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);

实现类:

@Overridepublic 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后再操作

 

相关文章:

java 黑马头条 day3 实名认证分布式事务问题 seata

1 完善实名认证功能 1.1 实名认证分布式事务问题 1.1.1 问题分析 在昨天的实名认证代码中&#xff0c;审核完毕后添加 id5的演示异常&#xff0c;重新使用postman进行测试, 会发现 出现异常后 本地方法因为有 Transactional注解 对ap_user ap_user_realname的操作会回滚 而…...

测试开发之Django实战示例 第七章 创建电商网站

第七章 创建电商网站在上一章里&#xff0c;创建了用户关注系统和行为流应用&#xff0c;还学习了使用Django的信号功能与使用Redis数据库存储图片浏览次数和排名。这一章将学习如何创建一个基础的电商网站。本章将学习创建商品品类目录&#xff0c;通过session实现购物车功能。…...

【C++之容器篇】造轮子:list的模拟实现与使用

目录前言一、关于list1. 简介2. 成员类型二、默认成员函数1. 构造函数1. list()2. list(size_t n,const T& val T())和list(InputIterator first,InputIterator last)2. 拷贝构造函数3. 析构函数4. 赋值运算符重载函数三、迭代器1. 普通对象的正向迭代器2. const对象的正向…...

自动驾驶:决策规划算法岗位面经分享

本专栏分享 计算机小伙伴秋招春招找工作的面试经验和面试的详情知识点 专栏首页: 主要分享计算机算法类在面试互联网公司时候一些真实的经验 人情况是985本硕,硕士研究方向是强化学习在移动机器人路径规划中的应用,一段自动驾驶中小厂实习经历,秋招找的大都是机器人和自动驾…...

2.7、进程调度的时机、切换与过程、方式

1、进程调度的时机 进程调度\color{red}进程调度进程调度&#xff08;低级调度&#xff09;&#xff0c;就是按照某种算法从就绪队列中选择一个进程为其分配处理机 进程在操作系统内核程序临界区\color{red}操作系统内核程序临界区操作系统内核程序临界区中不能\color{red}不能…...

工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发

工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#xff1a;实现对数据字典标签的增删改查操作 2、编码管理&#xff1a;实现对系统编码的增删改查操作 3、用户管理&#xff1a;管理和查看用户角色 4、菜单管理&#xff1a;实现对系统菜单的增删改查操…...

ESP32S3系列--SPI从机驱动详解(一)

一、目的 在之前的博文中《ESP32S3系列--SPI主机驱动详解(一)》、《ESP32S3系列--SPI主机驱动详解(二)》我们详细讲解了ESP32S3上的SPI外设如何工作在主机模式并通过代码的形式帮助大家理解。 本篇我们将介绍SPI外设工作在从机模式下的使用知识点。 二、介绍 参考资料 http…...

【实战篇】移动端H5网页在ios滑动不流畅和禁止缩放问题

问题描述:移动端H5网页在ios滑动不流畅和禁止缩放问题 最近开发小程序,有一个富文本展示页面使用的是<webview>H5网页嵌入的,当你用 overflow-y:scroll 属性的时候,内容超出容器溢出滚动的效果很迟顿,特别是在IOS系统中,而且页面还会缩放。 解决方案: 1…...

12 循环神经网络(基础篇) Basic RNN

文章目录问题引入关于权重权重共享RNN CellRNN原理RNN计算过程代码实现RNN Cell维度说明代码RNN维度说明NumLayers说明计算过程代码参考实例问题分析多分类问题代码RNN CellRNN改进Embedding网络结构Embedding说明Linear说明代码课程来源&#xff1a; 链接课程文本参考&#xf…...

【C语言必经之路——第11节】初阶指针(2)

五、指针的运算1、指针与整数相加减看一下下面的代码&#xff1a;#include<stdio.h> int my_strlen(char* str) {int count0;while(*str!\0){count;str;//指针加减整数}return count; } int main() {int lenmy_strlen("abcdef");printf("%d\n",len);…...

SpringBoot学习(1)第一个SpringBoot程序

之前的SpringMVc就不在记录了&#xff0c;好像时间不太够了&#xff0c;但是springmvc作为javaweb的升级学一学对于springboot还是有较大的帮助的。 首先我们需要引入依赖&#xff0c;但是请注意&#xff0c;其中的一个不算是依赖&#xff0c;写法有所不同 首先需要引入 <…...

什么是热迁移?90%的企业都理解错误

科技的发展&#xff0c;新冠的冲击&#xff0c;让市场竞争愈发激烈。尽管云计算服务为企业免除了基础硬件的建设和维护成本&#xff0c;当企业需要进行业务跨架调整、升级维护、环境测试等场景而进行云迁移&#xff0c;其过程中所带来的停机时间&#xff0c;就变得尤为头疼了。…...

Scratch少儿编程案例-丝滑版贪吃蛇

专栏分享 点击跳转=>Unity3D特效百例点击跳转=>案例项目实战源码点击跳转=>游戏脚本-辅助自动化点击跳转=>Android控件全解手册点击跳转=>Scratch编程案例👉关于作者...

Linux系统之网卡子接口配置方法

Linux系统之网卡子接口配置方法一、本地系统环境检查1.检查系统版本2.检查系统内核版本3.检查本地IP地址二、网卡子接口介绍1.网卡子接口简介2.网卡子接口的优点3.网卡子接口的缺点三 加载802.1q 模块1.查看系统802.1q 模块信息2.加载802.1q 模块3.检查802.1q 模块加载状态四、…...

2023上半年软考中级系统集成项目管理工程师2月25日开班

系统集成项目管理工程师是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目之一&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职…...

YOLO-V5轻松上手

之前介绍了YOLO-V1~V4版本各做了哪些事以及相较于之前版本的改进。有的人或许会想“直接学习最近版本的算法不好吗”&#xff0c;在我看来&#xff0c;每一个年代的版本/算法都凝聚着当年学术界的智慧&#xff0c;即便是它被淘汰了也依旧有值得思考的地方&#xff0c;或是可以使…...

CSS的优先级理解

权重 的 4个等级定义我们把特殊性分为4个等级&#xff0c;每一个等级代表一类选择器&#xff0c;每个等级的值相加得出选择器的权重。4个等级的定义如下&#xff1a;第一等级&#xff1a;代表内联样式&#xff0c;如style""&#xff0c;权值为 1000第二等级&#xff…...

前端工程师leetcode算法面试必备-二分搜索算法(中)

一、前言 二分搜索算法本身并不是特别复杂&#xff0c;核心点主要集中在&#xff1a; 有序数组&#xff1a;指的是一个递增或者递减的区间&#xff08;特殊情况如&#xff1a;【852. 山脉数组的峰顶索引】&#xff09;&#xff1b; 中间数&#xff1a;用来确定搜索目标落在左…...

【数据库】MySQL 单表查询,多表查询

目录 单表查询 一&#xff0c;创建表worker 1&#xff0c;创建表worker的sql代码如下&#xff1a; 2&#xff0c;向worker表中插入信息 二&#xff0c; 按要求进行单表查询 1、显示所有职工的基本信息。 2、查询所有职工所属部门的部门号&#xff0c;不显示重复的部门号。 …...

【c++】vector实现(源码剖析+手画图解)

vector是我接触的第一个容器&#xff0c;好好对待&#xff0c;好好珍惜&#xff01; 目录 文章目录 前言 二、vector如何实现 二、vector的迭代器&#xff08;原生指针&#xff09; 三、vector的数据结构 图解&#xff1a; 四、vector的构造及内存管理 1.push_back() …...

VScode查看python f.write()的文件乱码

VScode查看python f.write()的文件乱码 在使用 VScode 编写 python 代码&#xff0c; print&#xff08;&#xff09;&#xff0c;汉字正常显示&#xff0c; 使用 with open&#xff08;&#xff09;as f&#xff1a; f.write&#xff08;&#xff09;文件后&#xff0c; 在 …...

excel应用技巧:如何用函数制作简易抽奖动图

利用INDEX函数和随机整数函数RANDBETWEEN配合&#xff0c;在Excel中做一个简单的抽奖器&#xff0c;可以随机抽取姓名或者奖品。有兴趣的伙伴可以做出来试试&#xff0c;撞撞2023年好运气。每次年会大家最期待的就是抽奖环节。为了看看自己今年运气怎么样&#xff0c;会不会获奖…...

CSI Tool 安装及配置记录

一、Ubuntu安装 1.下载Ubuntu 首先安装Ubuntu 14.04 LTS 64位下载地址&#xff08;页面中第一个链接&#xff09; 2.制作启动盘&#xff08;注意备份&#xff09; 可以使用官方的工具Rufus&#xff0c;下载地址&#xff1a;https://rufus.ie/ 打开Rufus&#xff0c;先备份…...

华为OD机试 - 最低位排序(Python)| 真题+思路+代码

最低位排序 题目 给定一个非空数组(列表),起元素数据类型为整型, 请按照数组元素十进制最低位从小到大进行排序, 十进制最低位相同的元素,相对位置保持不变, 当数组元素为负值时,十进制最低为等同于去除符号位后对应十进制值最低位。 输入 给定一个非空数组(列表) 其…...

C#开发的OpenRA使用TrimExcess方法

C#开发的OpenRA使用TrimExcess方法 当你在细看OpenRA的代码,就会发现在下面这段代码添加了一个方法: foreach (var nodes in levels) nodes.TrimExcess(); 在上面代码里遍历整个节点列表,把所有节点都调用TrimExcess方法处理一下, 这样做的意义何在?为什么我们在一般的代码…...

ImageMagick任意文件读取漏洞(CVE-2022-44268)

0x00 前提 前几天爆出一个 ImageMagick 漏洞 &#xff0c;可以造成一个任意文件读取的危害比较可观&#xff0c;最近有时间来复现学习一下 主要是影响的范围很大&#xff0c;很多地方都有这个问题&#xff0c;需要来学习一下 0x01 介绍 ImageMagick 是一个免费的开源软件套…...

第十九篇 ResNet——论文翻译

文章目录 摘要1 引言2 相关工作3 深度残差学习3.1 残差学习3.2 快捷恒等映射3.3 网络架构3.4 实现4 实验4.1 ImageNet 分类4.2 CIFAR-10 和分析4.3 PASCAL 和 MS COCO 上的物体检测🐇🐇🐇🐇🐇🐇 🐇 欢迎阅读 【AI浩】 的博客🐇 👍 阅读完毕,可以动动小手赞一…...

RiProRiProV2主题美化顶部增加一行导航header导航通知

背景: 有些网站的背景顶部有一行罪行公告,样式不错,希望自己的网站也借鉴过来,本教程将指导如何操作,并调整成自己想要的样式。 比如网友搭的666资源站 xd素材中文网...

RT-Thread MSH_CMD_EXPORT分析

RT-Thread MSH_CMD_EXPORT分析 1. 源码分析 在rt-thread中&#xff0c;使用FinSH&#xff0c;可以支持命令行。在源码中&#xff0c;使用MSH_CMD_EXPORT导出函数到对应命令。 extern void rt_show_version(void); long version(void) {rt_show_version();return 0; } MSH_CM…...

电脑麦克风没声音怎么办?这3招就可以解决!

最近有用户在使用电脑麦克风进行视频录制时&#xff0c;发现麦克风没有声音。这是什么原因&#xff1f;电脑麦克风没有声音怎么办&#xff1f;关于解决方案&#xff0c;我专门整理了三种方法来帮你们&#xff0c;一起来看看吧&#xff01; 操作环境&#xff1a; 演示机型&#…...

网站原创文章/网络营销推广机构

原标题&#xff1a;为什么说韩国留学性价比极高&#xff1f;是最适合工薪家庭的留学选择&#xff01;为什么韩国的电影电视剧就是好嗑&#xff1f;为什么韩国欧巴的迷妹遍布世界各国&#xff1f;为什么韩国留学受追捧&#xff1f;为什么说韩国是性价比极高的留学选择&#xff1…...

日新月异网站建设/兴安盟新百度县seo快速排名

文&#xff1a;祈澈姑娘前端学习资源实在是又多又广&#xff0c;在这样的一个知识的海洋里&#xff0c;我们像一块海绵一样吸收&#xff0c;想要快速提高效率&#xff0c;平时的总结不可缺少&#xff0c;以下总结了一些&#xff0c;排版自我感觉良好&#xff0c;推送出来&#…...

网站开发投标书/推广渠道平台

spring的整体架构 架构图&#xff1a; 这些模块可以分为以下几个部分&#xff1a; core container core container 包括 core beans EL context core 和 beans 模块是框架的基础部分&#xff0c;提供 IOC 控制反转 和依赖注入的特性。 core模块中包含spring框架的基本核心类&am…...

来年做啥网站致富/百度竞价入门教程

在Java程序运行过程中&#xff0c;对象会不断的被新建和回收&#xff0c;而对象大部分情况下是放在堆空间中的&#xff0c;所以一旦对象太多导致堆空间不足&#xff0c;就会抛出OOM异常&#xff0c;也就是堆内存溢出了。 就像查案一样&#xff0c;程序出现问题的时候&#xff…...

高端的丹阳网站建设/买卖网交易平台

戳蓝字“hi 知兮寒兮”关注我们哦&#xff01;前言通过本篇的学习&#xff0c;你将学会Base64在实战中的使用&#xff0c;此工具包提供了常用的方法&#xff0c;如下&#xff1a;text明文【转】Base64字符串&#xff1b;text的Base64字符串【转】明文&#xff1b;文件(图片、pd…...

自主建设公司网站/黄冈网站推广策略

开门见山&#xff0c;文档不足 下载下来的是zip包&#xff0c;不是tar包&#xff0c;未能解决 下载 wget https://releases.hashicorp.com/consul/1.3.0/consul_1.3.0_linux_amd64.zip 安装uzip yum -y install wget 解压 unzip consul_1.3.0_linux_amd64.zip 测试 ./c…...