1 项目简介
1.1 功能结构图


1.2 功能架构图

1.3 课程安排

1.4 项目结构

1.5 项目运行
01.前端
虚拟机 root / itcast
虚拟机 itcast / itcast
---------------------------------------------------------------------------------------------------------
app端 http://127.0.0.1:8801 13511223456 / admin
自媒体 http://127.0.0.1:8802 admin / admin
平台管理 http://127.0.0.1:8803 guest / guest
---------------------------------------------------------------------------------------------------------
xxl-job http://127.0.0.1:8888/xxl-job-admin/ admin / 123456
02.后端
AppGatewayApplication 51601
WemediaGatewayAplication 51602
AdminGatewayApplication 51603
-------------------------------------------------------------------------------------------------------------
UserApplication 51801 http://localhost:51801/swagger-ui.html http://localhost:51801/doc.html
ArticleApplication 51802
WemediaApplication 51803
SearchApplication 51804
BehaviorApplication 51805
CommentApplication 51806
AdminApplication 51809
ScheduleApplication 51701
---------------------------------------------------------------------------------------------------------
FreemarkerApplication 8881 http://127.0.0.1:8881/basic http://127.0.0.1:8881/list
03.测试
http://localhost:51801/api/v1/login/login_auth
被gateway路由到
http://localhost:51601/user/api/v1/login/login_auth
2 数据库
垂直分表:将一个表的字段分散到多个表中,每个表存储其中一部分字段。
优势:
1.减少IO争抢,减少锁表的几率,查看文章概述与文章详情互不影响
2.充分发挥高频数据的操作效率,对文章概述数据操作的高效率不会被操作文章详情数据的低效率所拖累。
拆分规则:
1.把不常用的字段单独放在一张表
2.把text,blob等大字段拆分出来单独放在一张表
3.经常组合查询的字段单独放在一张表中
2.1 leadnews_user
2.1.1 ap_user:APP用户信息表

2.1.2 ap_user_fan:APP用户粉丝信息表

2.1.3 ap_user_follow:APP用户关注信息表

2.1.4 ap_user_realname:APP实名认证信息表

2.2 leadnews_article
2.2.1 ap_article:文章信息表

2.2.2 ap_article_config:APP已发布文章配置表

2.2.3 ap_article_content:APP已发布文章内容表

2.2.4 ap_author:APP文章作者信息表

2.2.5 ap_collection:APP收藏信息表


2.3.1 wm_channel:频道信息表

2.3.2 wm_fans_statistics:自媒体粉丝数据统计表

2.3.3 wm_material:自媒体图文素材信息表

2.3.4 wm_news:自媒体图文内容信息表

2.3.5 wm_news_material:自媒体图文引用素材信息表

2.3.6 wm_news_statistics:自媒体图文数据统计表

2.3.7 wm_user:自媒体用户信息表

3 图片上传:MinIO
3.1 依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.1.0</version>
</dependency>
3.2 测试类
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("D:\\list.html");;
//1.创建minio链接客户端
MinioClient minioClient = MinioClient.builder().credentials("minioadmin", "minioadmin").endpoint("http://127.0.0.1:9000").build();
//2.上传
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object("list.html")//文件名
.contentType("text/html")//文件类型
.bucket("leadnews")//桶名词 与minio创建的名词一致
.stream(fileInputStream, fileInputStream.available(), -1) //文件流
.build();
minioClient.putObject(putObjectArgs);
System.out.println("http://127.0.0.1:9000/leadnews/list.html");
} catch (Exception ex) {
ex.printStackTrace();
}
}
3.3 测试:Test
D:\software_ware\Typora\02.Directory02\Chapter06\00.Source.assets\minio-file
-------------------------------------------------------------------------------------------------------------
01.配置文件
minio:
accessKey: minioadmin
secretKey: minioadmin
bucket: leadnews
endpoint: http://127.0.0.1:9000
readPath: http://127.0.0.1:9000
02.测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MinIOApplication.class)
public class MinIOTest {
@Autowired
private FileStorageService fileStorageService;
@Test
public void testUpdateImgFile() {
try {
FileInputStream fileInputStream = new FileInputStream("C:\\document\\Background\\01.bio\\09.jpg");
String filePath = fileStorageService.uploadImgFile("", "09.jpg", fileInputStream);
System.out.println(filePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
3.4 测试:Controller层
@RestController
@RequestMapping("/api/v1/material")
public class WmMaterialController {
@Autowired
private FileStorageService fileStorageService;
@PostMapping("/upload_picture")
public ResponseResult uploadPicture(MultipartFile multipartFile) {
//1.检查参数
if(multipartFile == null || multipartFile.getSize() == 0){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2.上传图片到minIO中
String fileName = UUID.randomUUID().toString().replace("-", "");
//aa.jpg
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) {
e.printStackTrace();
log.error("WmMaterialServiceImpl-上传文件失败");
}
//3.保存到数据库中
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);
//4.返回结果
return ResponseResult.okResult(wmMaterial);
}
}
4 后端实现
4.1.1 AppJwtUtil
public class AppJwtUtil {
// TOKEN的有效期一天(S)
private static final int TOKEN_TIME_OUT = 3_600;
// 加密KEY
private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
// 最小刷新间隔(S)
private static final int REFRESH_TIME = 300;
// 生产ID
public static String getToken(Long id){
Map<String, Object> claimMaps = new HashMap<>();
claimMaps.put("id",id);
long currentTime = System.currentTimeMillis();
return Jwts.builder()
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date(currentTime)) //签发时间
.setSubject("system") //说明
.setIssuer("heima") //签发者信息
.setAudience("app") //接收用户
.compressWith(CompressionCodecs.GZIP) //数据压缩方式
.signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
.setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳
.addClaims(claimMaps) //cla信息
.compact();
}
/**
* 获取token中的claims信息
*
* @param token
* @return
*/
private static Jws<Claims> getJws(String token) {
return Jwts.parser()
.setSigningKey(generalKey())
.parseClaimsJws(token);
}
/**
* 获取payload body信息
*
* @param token
* @return
*/
public static Claims getClaimsBody(String token) {
try {
return getJws(token).getBody();
}catch (ExpiredJwtException e){
return null;
}
}
/**
* 获取hearder body信息
*
* @param token
* @return
*/
public static JwsHeader getHeaderBody(String token) {
return getJws(token).getHeader();
}
/**
* 是否过期
*
* @param claims
* @return -1:有效,0:有效,1:过期,2:过期
*/
public static int verifyToken(Claims claims) {
if(claims==null){
return 1;
}
try {
claims.getExpiration()
.before(new Date());
// 需要自动刷新TOKEN
if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
return -1;
}else {
return 0;
}
} catch (ExpiredJwtException ex) {
return 1;
}catch (Exception e){
return 2;
}
}
/**
* 由字符串生成加密key
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
}
4.1.2 AuthorizeFilter
@Component
@Slf4j
public class AuthorizeFilter implements Ordered, GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取request和response对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//2.判断是否是登录
if(request.getURI().getPath().contains("/login")){
//放行
return chain.filter(exchange);
}
//3.获取token
String token = request.getHeaders().getFirst("token");
//4.判断token是否存在
if(StringUtils.isBlank(token)){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//5.判断token是否有效
try {
Claims claimsBody = AppJwtUtil.getClaimsBody(token);
//是否是过期
int result = AppJwtUtil.verifyToken(claimsBody);
if(result == 1 || result == 2){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
}catch (Exception e){
e.printStackTrace();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//6.放行
return chain.filter(exchange);
}
/**
* 优先级设置 值越小 优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
@Component
@Slf4j
public class AuthorizeFilter implements Ordered, GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取request和response对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//2.判断是否是登录
if(request.getURI().getPath().contains("/login")){
//放行
return chain.filter(exchange);
}
//3.获取token
String token = request.getHeaders().getFirst("token");
//4.判断token是否存在
if(StringUtils.isBlank(token)){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//5.判断token是否有效
try {
Claims claimsBody = AppJwtUtil.getClaimsBody(token);
//是否是过期
int result = AppJwtUtil.verifyToken(claimsBody);
if(result == 1 || result == 2){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//获取用户信息
Object userId = claimsBody.get("id");
//存储header中
ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
httpHeaders.add("userId", userId + "");
}).build();
//重置请求
exchange.mutate().request(serverHttpRequest);
} catch (Exception e) {
e.printStackTrace();
}
//6.放行
return chain.filter(exchange);
}
/**
* 优先级设置 值越小 优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
4.2.1 WmThreadLocalUtil
public class WmThreadLocalUtil {
private final static ThreadLocal<WmUser> WM_USER_THREAD_LOCAL = new ThreadLocal<>();
//存入线程中
public static void setUser(WmUser wmUser){
WM_USER_THREAD_LOCAL.set(wmUser);
}
//从线程中获取
public static WmUser getUser(){
return WM_USER_THREAD_LOCAL.get();
}
//清理
public static void clear(){
WM_USER_THREAD_LOCAL.remove();
}
}
4.2.2 HandlerInterceptor
public class WmTokenInterceptor implements HandlerInterceptor {
/**
* 得到header中的用户信息,并且存入到当前线程中
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userId = request.getHeader("userId");
if(userId != null){
//存入到当前线程中
WmUser wmUser = new WmUser();
wmUser.setId(Integer.valueOf(userId));
WmThreadLocalUtil.setUser(wmUser);
}
return true;
}
/**
* 清理线程中的数据
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
WmThreadLocalUtil.clear();
}
}
4.2.3 注册自定义拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new WmTokenInterceptor()).addPathPatterns("/**");
}
}
4.3 自媒体自动审核
4.3.1 审核流程

4.3.2 文本审核、图片审核
https://help.aliyun.com/document_detail/70439.html
https://help.aliyun.com/document_detail/70292.html