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
资料文件夹下导入 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.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 |
---|---|---|---|---|---|
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*/@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)获取登录用户思路分析
在去保存图片之前,需要先知道是哪一位自媒体人登录了当前系统,也就是需要确定哪个自媒体人上传了图片
操作步骤:
-
上传图片需要携带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 {@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 |
---|---|---|---|---|---|
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 {@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 测试
启动清单:
-
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);
实现类:
@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 问题分析 在昨天的实名认证代码中,审核完毕后添加 id5的演示异常,重新使用postman进行测试, 会发现 出现异常后 本地方法因为有 Transactional注解 对ap_user ap_user_realname的操作会回滚 而…...

测试开发之Django实战示例 第七章 创建电商网站
第七章 创建电商网站在上一章里,创建了用户关注系统和行为流应用,还学习了使用Django的信号功能与使用Redis数据库存储图片浏览次数和排名。这一章将学习如何创建一个基础的电商网站。本章将学习创建商品品类目录,通过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}进程调度进程调度(低级调度),就是按照某种算法从就绪队列中选择一个进程为其分配处理机 进程在操作系统内核程序临界区\color{red}操作系统内核程序临界区操作系统内核程序临界区中不能\color{red}不能…...
工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发
工程项目各模块及其功能点清单 一、系统管理 1、数据字典:实现对数据字典标签的增删改查操作 2、编码管理:实现对系统编码的增删改查操作 3、用户管理:管理和查看用户角色 4、菜单管理:实现对系统菜单的增删改查操…...
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说明代码课程来源: 链接课程文本参考…...

【C语言必经之路——第11节】初阶指针(2)
五、指针的运算1、指针与整数相加减看一下下面的代码:#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就不在记录了,好像时间不太够了,但是springmvc作为javaweb的升级学一学对于springboot还是有较大的帮助的。 首先我们需要引入依赖,但是请注意,其中的一个不算是依赖,写法有所不同 首先需要引入 <…...

什么是热迁移?90%的企业都理解错误
科技的发展,新冠的冲击,让市场竞争愈发激烈。尽管云计算服务为企业免除了基础硬件的建设和维护成本,当企业需要进行业务跨架调整、升级维护、环境测试等场景而进行云迁移,其过程中所带来的停机时间,就变得尤为头疼了。…...
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日开班
系统集成项目管理工程师是全国计算机技术与软件专业技术资格(水平)考试(简称软考)项目之一,是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试,既属于国家职业资格考试,又是职…...

YOLO-V5轻松上手
之前介绍了YOLO-V1~V4版本各做了哪些事以及相较于之前版本的改进。有的人或许会想“直接学习最近版本的算法不好吗”,在我看来,每一个年代的版本/算法都凝聚着当年学术界的智慧,即便是它被淘汰了也依旧有值得思考的地方,或是可以使…...
CSS的优先级理解
权重 的 4个等级定义我们把特殊性分为4个等级,每一个等级代表一类选择器,每个等级的值相加得出选择器的权重。4个等级的定义如下:第一等级:代表内联样式,如style"",权值为 1000第二等级ÿ…...

前端工程师leetcode算法面试必备-二分搜索算法(中)
一、前言 二分搜索算法本身并不是特别复杂,核心点主要集中在: 有序数组:指的是一个递增或者递减的区间(特殊情况如:【852. 山脉数组的峰顶索引】); 中间数:用来确定搜索目标落在左…...

【数据库】MySQL 单表查询,多表查询
目录 单表查询 一,创建表worker 1,创建表worker的sql代码如下: 2,向worker表中插入信息 二, 按要求进行单表查询 1、显示所有职工的基本信息。 2、查询所有职工所属部门的部门号,不显示重复的部门号。 …...

【c++】vector实现(源码剖析+手画图解)
vector是我接触的第一个容器,好好对待,好好珍惜! 目录 文章目录 前言 二、vector如何实现 二、vector的迭代器(原生指针) 三、vector的数据结构 图解: 四、vector的构造及内存管理 1.push_back() …...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...

SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
TJCTF 2025
还以为是天津的。这个比较容易,虽然绕了点弯,可还是把CP AK了,不过我会的别人也会,还是没啥名次。记录一下吧。 Crypto bacon-bits with open(flag.txt) as f: flag f.read().strip() with open(text.txt) as t: text t.read…...

SQL注入篇-sqlmap的配置和使用
在之前的皮卡丘靶场第五期SQL注入的内容中我们谈到了sqlmap,但是由于很多朋友看不了解命令行格式,所以是纯手动获取数据库信息的 接下来我们就用sqlmap来进行皮卡丘靶场的sql注入学习,链接:https://wwhc.lanzoue.com/ifJY32ybh6vc…...