码神之路博客
1、父工程依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mszlu</groupId>
<artifactId>blog-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>blog-api</module>
<module>blog-admin</module>
</modules>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.10</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、blog-admin工程
2.1、依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>blog-parent</artifactId>
<groupId>com.mszlu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blog-admin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<!-- 排除 默认使用的logback -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
</project>
2.2、config 文件夹
2.2.1、MybatisPlusConfig
package com.mszlu.blog.admin.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.mszlu.blog.admin.mapper")
public class MybatisPlusConfig {
//分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
2.2.2、SecurityConfig
package com.mszlu.blog.admin.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
public static void main(String[] args) {
//加密策略 MD5 不安全 彩虹表 MD5 加盐
String mszlu = new BCryptPasswordEncoder().encode("mszlu");
System.out.println(mszlu);
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //开启登录认证
// .antMatchers("/user/findAll").hasRole("admin") //访问接口需要admin的角色
.antMatchers("/css/**").permitAll()
.antMatchers("/img/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/plugins/**").permitAll()
.antMatchers("/admin/**").access("@authService.auth(request,authentication)") //自定义service 来去实现实时的权限认证
.antMatchers("/pages/**").authenticated()
.and().formLogin()
.loginPage("/login.html") //自定义的登录页面
.loginProcessingUrl("/login") //登录处理接口
.usernameParameter("username") //定义登录时的用户名的key 默认为username
.passwordParameter("password") //定义登录时的密码key,默认是password
.defaultSuccessUrl("/pages/main.html")
.failureUrl("/login.html")
.permitAll() //通过 不拦截,更加前面配的路径决定,这是指和登录表单相关的接口 都通过
.and().logout() //退出登录配置
.logoutUrl("/logout") //退出登录接口
.logoutSuccessUrl("/login.html")
.permitAll() //退出登录的接口放行
.and()
.httpBasic()
.and()
.csrf().disable() //csrf关闭 如果自定义登录 需要关闭
.headers().frameOptions().sameOrigin();// 支持iframe页面嵌套
}
}
2.3、controller
2.3.1 AdminController
package com.mszlu.blog.admin.controller;
import com.mszlu.blog.admin.model.params.PageParam;
import com.mszlu.blog.admin.pojo.Permission;
import com.mszlu.blog.admin.service.PermissionService;
import com.mszlu.blog.admin.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("admin")
public class AdminController {
@Autowired
private PermissionService permissionService;
@PostMapping("permission/permissionList")
public Result listPermission(@RequestBody PageParam pageParam){
return permissionService.listPermission(pageParam);
}
@PostMapping("permission/add")
public Result add(@RequestBody Permission permission){
return permissionService.add(permission);
}
@PostMapping("permission/update")
public Result update(@RequestBody Permission permission){
return permissionService.update(permission);
}
@GetMapping("permission/delete/{id}")
public Result delete(@PathVariable("id") Long id){
return permissionService.delete(id);
}
}
2.4、mapper
2.4.1 AdminMapper
package com.mszlu.blog.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.admin.pojo.Admin;
import com.mszlu.blog.admin.pojo.Permission;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface AdminMapper extends BaseMapper<Admin> {
@Select("SELECT * FROM ms_permission where id in (select permission_id from ms_admin_permission where admin_id=#{adminId})")
List<Permission> findPermissionByAdminId(Long adminId);
}
2.4.2、PermissionMapper
package com.mszlu.blog.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.admin.pojo.Permission;
public interface PermissionMapper extends BaseMapper<Permission> {
}
2.5、model
2.5.1、PageParam
package com.mszlu.blog.admin.model.params;
import lombok.Data;
@Data
public class PageParam {
private Integer currentPage;
private Integer pageSize;
private String queryString;
}
2.6、pojo
2.6.1、Admin
package com.mszlu.blog.admin.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
@Data
public class Admin {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
}
2.6.2、Permission
package com.mszlu.blog.admin.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
@Data
public class Permission {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String path;
private String description;
}
2.7、service
2.7.1 AdminService
package com.mszlu.blog.admin.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.blog.admin.mapper.AdminMapper;
import com.mszlu.blog.admin.pojo.Admin;
import com.mszlu.blog.admin.pojo.Permission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AdminService {
@Autowired
private AdminMapper adminMapper;
public Admin findAdminByUsername(String username){
LambdaQueryWrapper<Admin> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Admin::getUsername,username);
queryWrapper.last("limit 1");
Admin admin = (Admin) adminMapper.selectOne(queryWrapper);
return admin;
}
public List<Permission> findPermissionByAdminId(Long adminId) {
//SELECT * FROM `ms_permission` where id in (select permission_id from ms_admin_permission where admin_id=1)
return adminMapper.findPermissionByAdminId(adminId);
}
}
2.7.2、AuthService
package com.mszlu.blog.admin.service;
import com.mszlu.blog.admin.pojo.Admin;
import com.mszlu.blog.admin.pojo.Permission;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@Service
public class AuthService {
@Autowired
private AdminService adminService;
public boolean auth(HttpServletRequest request, Authentication authentication){
//权限认证
//请求路径
String requestURI = request.getRequestURI();
Object principal = authentication.getPrincipal();
if (principal == null || "anonymousUser".equals(principal)){
//未登录
return false;
}
UserDetails userDetails = (UserDetails) principal;
String username = userDetails.getUsername();
Admin admin = adminService.findAdminByUsername(username);
if (admin == null){
return false;
}
if (1 == admin.getId()){
//超级管理员
return true;
}
Long id = admin.getId();
List<Permission> permissionList = this.adminService.findPermissionByAdminId(id);
requestURI = StringUtils.split(requestURI,'?')[0];
for (Permission permission : permissionList) {
if (requestURI.equals(permission.getPath())){
return true;
}
}
return false;
}
}
2.7.3、PermissionService
package com.mszlu.blog.admin.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mszlu.blog.admin.controller.AdminController;
import com.mszlu.blog.admin.mapper.PermissionMapper;
import com.mszlu.blog.admin.model.params.PageParam;
import com.mszlu.blog.admin.pojo.Permission;
import com.mszlu.blog.admin.vo.PageResult;
import com.mszlu.blog.admin.vo.Result;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.security.krb5.internal.PAData;
@Service
public class PermissionService {
@Autowired
private PermissionMapper permissionMapper;
public Result listPermission(PageParam pageParam) {
/**
* 要的数据,管理台 表的所有的字段 Permission
* 分页查询
*/
Page<Permission> page = new Page<>(pageParam.getCurrentPage(),pageParam.getPageSize());
LambdaQueryWrapper<Permission> queryWrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(pageParam.getQueryString())){
queryWrapper.eq(Permission::getName,pageParam.getQueryString());
}
Page<Permission> permissionPage = permissionMapper.selectPage(page, queryWrapper);
PageResult<Permission> pageResult = new PageResult<>();
pageResult.setList(permissionPage.getRecords());
pageResult.setTotal(permissionPage.getTotal());
return Result.success(pageResult);
}
public Result add(Permission permission) {
this.permissionMapper.insert(permission);
return Result.success(null);
}
public Result update(Permission permission) {
this.permissionMapper.updateById(permission);
return Result.success(null);
}
public Result delete(Long id) {
this.permissionMapper.deleteById(id);
return Result.success(null);
}
}
2.7.4、SecurityUserService
package com.mszlu.blog.admin.service;
import com.mszlu.blog.admin.pojo.Admin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@Component
public class SecurityUserService implements UserDetailsService {
@Autowired
private AdminService adminService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//登录的时候,会把username 传递到这里
//通过username查询 admin表,如果 admin存在 将密码告诉spring security
//如果不存在 返回null 认证失败了
Admin admin = this.adminService.findAdminByUsername(username);
if (admin == null){
//登录失败
return null;
}
UserDetails userDetails = new User(username,admin.getPassword(),new ArrayList<>());
return userDetails;
}
}
2.8、vo
2.8.1、PageResult
package com.mszlu.blog.admin.vo;
import lombok.Data;
import java.util.List;
@Data
public class PageResult<T> {
private List<T> list;
private Long total;
}
2.8.2 Result
package com.mszlu.blog.admin.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Result {
private boolean success;
private int code;
private String msg;
private Object data;
public static Result success(Object data){
return new Result(true,200,"success",data);
}
public static Result fail(int code, String msg){
return new Result(false,code,msg,null);
}
}
2.9、adminApp
package com.mszlu.blog.admin;
import com.alibaba.fastjson.annotation.JSONField;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AdminApp {
public static void main(String[] args) {
SpringApplication.run(AdminApp.class,args);
}
}
2.10、application.yml
server.port=8889
spring.application.name=mszlu_admin_blog
#数据库的配置
# datasource
spring.datasource.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#mybatis-plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.table-prefix=ms_
3、blog-api
3.1、依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>blog-parent</artifactId>
<groupId>com.mszlu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blog-api</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<!-- 排除 默认使用的logback -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.10</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>[7.7.0, 7.7.99]</version>
</dependency>
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.apache.rocketmq</groupId>-->
<!-- <artifactId>rocketmq-spring-boot-starter</artifactId>-->
<!-- <version>2.2.0</version>-->
<!-- </dependency>-->
</dependencies>
</project>
3.2、common
3.2.1 aop
3.2.1 LogAnnotation
package com.mszlu.blog.common.aop;
import java.lang.annotation.*;
//Type 代表可以放在类上面 Method 代表可以放在方法上
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
String module() default "";
String operator() default "";
}
3.2.2 LogAspect
package com.mszlu.blog.common.aop;
import com.alibaba.fastjson.JSON;
import com.mszlu.blog.utils.HttpContextUtils;
import com.mszlu.blog.utils.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
@Component
@Aspect //切面 定义了通知和切点的关系
@Slf4j
public class LogAspect {
@Pointcut("@annotation(com.mszlu.blog.common.aop.LogAnnotation)")
public void pt(){}
//环绕通知
@Around("pt()")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = joinPoint.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
recordLog(joinPoint, time);
return result;
}
private void recordLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
log.info("=====================log start================================");
log.info("module:{}",logAnnotation.module());
log.info("operation:{}",logAnnotation.operator());
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
log.info("request method:{}",className + "." + methodName + "()");
// //请求的参数
Object[] args = joinPoint.getArgs();
String params = JSON.toJSONString(args[0]);
log.info("params:{}",params);
//获取request 设置IP地址
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
log.info("ip:{}", IpUtils.getIpAddr(request));
log.info("excute time : {} ms",time);
log.info("=====================log end================================");
}
}
3.2、cach
3.2.1、Cache
package com.mszlu.blog.common.cache;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cache {
long expire() default 1 * 60 * 1000;
//缓存标识 key
String name() default "";
}
3.2.2、CacheAspect
package com.mszlu.blog.common.cache;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mszlu.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.Duration;
//aop 定义一个切面,切面定义了切点和通知的关系
@Aspect
@Component
@Slf4j
public class CacheAspect {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Pointcut("@annotation(com.mszlu.blog.common.cache.Cache)")
public void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp){
try {
Signature signature = pjp.getSignature();
//类名
String className = pjp.getTarget().getClass().getSimpleName();
//调用的方法名
String methodName = signature.getName();
Class[] parameterTypes = new Class[pjp.getArgs().length];
Object[] args = pjp.getArgs();
//参数
String params = "";
for(int i=0; i<args.length; i++) {
if(args[i] != null) {
params += JSON.toJSONString(args[i]);
parameterTypes[i] = args[i].getClass();
}else {
parameterTypes[i] = null;
}
}
if (StringUtils.isNotEmpty(params)) {
//加密 以防出现key过长以及字符转义获取不到的情况
params = DigestUtils.md5Hex(params);
}
Method method = pjp.getSignature().getDeclaringType().getMethod(methodName, parameterTypes);
//获取Cache注解
Cache annotation = method.getAnnotation(Cache.class);
//缓存过期时间
long expire = annotation.expire();
//缓存名称
String name = annotation.name();
//先从redis获取
String redisKey = name + "::" + className+"::"+methodName+"::"+params;
String redisValue = redisTemplate.opsForValue().get(redisKey);
if (StringUtils.isNotEmpty(redisValue)){
log.info("走了缓存~~~,{}",redisKey);
Result result = JSON.parseObject(redisValue, Result.class);
return result;
}
Object proceed = pjp.proceed();
redisTemplate.opsForValue().set(redisKey,JSON.toJSONString(proceed), Duration.ofMillis(expire));
log.info("存入缓存~~~ {},{}",className,methodName);
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return Result.fail(-999,"系统错误");
}
}
3.3 config
3.3.1、MybatisPlusConfig
package com.mszlu.blog.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.mszlu.blog.dao.mapper")
public class MybatisPlusConfig {
//分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
3.3.2、ThreadPoolConfig
package com.mszlu.blog.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync //开启多线程
public class ThreadPoolConfig {
@Bean("taskExecutor")
public Executor asyncServiceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(5);
// 设置最大线程数
executor.setMaxPoolSize(20);
//配置队列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("码神之路博客项目");
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//执行初始化
executor.initialize();
return executor;
}
}
3.3.3、WebMVCConfig
package com.mszlu.blog.config;
import com.mszlu.blog.handler.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addCorsMappings(CorsRegistry registry) {
// //跨域配置
registry.addMapping("/**").allowedOrigins("https://blog.mszlu.com","http://blog1.mszlu.com","http://localhost:8080");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截test接口,后续实际遇到需要拦截的接口时,在配置为真正的拦截接口
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/test")
.addPathPatterns("/comments/create/change")
.addPathPatterns("/articles/publish");
}
}
controller
ArticleController
package com.mszlu.blog.controller;
import com.mszlu.blog.common.aop.LogAnnotation;
import com.mszlu.blog.common.cache.Cache;
import com.mszlu.blog.service.ArticleService;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.ArticleParam;
import com.mszlu.blog.vo.params.PageParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
//json数据进行交互
@RestController
@RequestMapping("articles")
public class ArticleController {
@PostMapping("search")
public Result search(@RequestBody ArticleParam articleParam){
//写一个搜索接口
String search = articleParam.getSearch();
return articleService.searchArticle(search);
}
@Autowired
private ArticleService articleService;
/**
* 首页 文章列表
* @param pageParams
* @return
*/
@PostMapping
//加上此注解 代表要对此接口记录日志
@LogAnnotation(module="文章",operator="获取文章列表")
@Cache(expire = 5 * 60 * 1000,name = "listArticle")
public Result listArticle(@RequestBody PageParams pageParams){
// int i = 10/0;
return articleService.listArticle(pageParams);
}
/**
* 首页 最热文章
* @return
*/
@PostMapping("hot")
@Cache(expire = 5 * 60 * 1000,name = "hot_article")
public Result hotArticle(){
int limit = 5;
return articleService.hotArticle(limit);
}
/**
* 首页 最新文章
* @return
*/
@PostMapping("new")
@Cache(expire = 5 * 60 * 1000,name = "news_article")
public Result newArticles(){
int limit = 5;
return articleService.newArticles(limit);
}
/**
* 首页 文章归档
* @return
*/
@PostMapping("listArchives")
public Result listArchives(){
return articleService.listArchives();
}
@PostMapping("view/{id}")
@Cache(expire = 5 * 60 * 1000,name = "view_article")
public Result findArticleById(@PathVariable("id") Long articleId){
return articleService.findArticleById(articleId);
}
//接口url:/articles/publish
//
//请求方式:POST
@PostMapping("publish")
public Result publish(@RequestBody ArticleParam articleParam){
return articleService.publish(articleParam);
}
@PostMapping("{id}")
public Result articleById(@PathVariable("id") Long articleId){
return articleService.findArticleById(articleId);
}
}
CategoryController
package com.mszlu.blog.controller;
import com.mszlu.blog.service.CategoryService;
import com.mszlu.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("categorys")
public class CategoryController {
@Autowired
private CategoryService categoryService;
// /categorys
@GetMapping
public Result categories(){
return categoryService.findAll();
}
@GetMapping("detail")
public Result categoriesDetail(){
return categoryService.findAllDetail();
}
///category/detail/{id}
@GetMapping("detail/{id}")
public Result categoryDetailById(@PathVariable("id") Long id){
return categoryService.categoryDetailById(id);
}
}
CommentsController
package com.mszlu.blog.controller;
import com.mszlu.blog.service.CommentsService;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.CommentParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("comments")
public class CommentsController {
@Autowired
private CommentsService commentsService;
///comments/article/{id}
@GetMapping("article/{id}")
public Result comments(@PathVariable("id") Long id){
return commentsService.commentsByArticleId(id);
}
@PostMapping("create/change")
public Result comment(@RequestBody CommentParam commentParam){
return commentsService.comment(commentParam);
}
}
LoginController
package com.mszlu.blog.controller;
import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.service.SysUserService;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.LoginParam;
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 SysUserService sysUserService;
@Autowired
private LoginService loginService;
@PostMapping
public Result login(@RequestBody LoginParam loginParam){
//登录 验证用户 访问用户表,但是
return loginService.login(loginParam);
}
}
LogoutController
package com.mszlu.blog.controller;
import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.LoginParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("logout")
public class LogoutController {
@Autowired
private LoginService loginService;
@GetMapping
public Result logout(@RequestHeader("Authorization") String token){
return loginService.logout(token);
}
}
MaxKeyController
package com.mszlu.blog.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@Controller
@RequestMapping("maxKey")
public class MaxKeyController {
//669240069155979264
//pp2kMTcxMjIwMjExNTIyMTE5OTkHNE
@RequestMapping("login")
public String login(){
String url = "http://localhost:8080/maxkey/authz/oauth/v20/authorize?client_id=669240069155979264&response_type=code&redirect_uri=http://localhost:8888/maxKey/code";
return "redirect:"+url;
}
@RequestMapping("code")
@ResponseBody
public String code(String code){
String url = "http://localhost:8080/maxkey/authz/oauth/v20/token?client_id=669240069155979264&client_secret=pp2kMTcxMjIwMjExNTIyMTE5OTkHNE&grant_type=authorization_code&redirect_uri=http://localhost:8888/maxKey/code&code="+code;
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
System.out.println(forEntity.getStatusCode() + ":"+forEntity.getBody());
return "应该跳转应用首页 代表登录完成";
}
}
RegisterController
package com.mszlu.blog.controller;
import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.LoginParam;
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("register")
public class RegisterController {
@Autowired
private LoginService loginService;
@PostMapping
public Result register(@RequestBody LoginParam loginParam){
//sso 单点登录,后期如果把登录注册功能 提出去(单独的服务,可以独立提供接口服务)
return loginService.register(loginParam);
}
}
TagsController
package com.mszlu.blog.controller;
import com.mszlu.blog.service.TagService;
import com.mszlu.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("tags")
public class TagsController {
@Autowired
private TagService tagService;
// /tags/hot
@GetMapping("hot")
public Result hot(){
int limit = 6;
return tagService.hots(limit);
}
@GetMapping
public Result findAll(){
return tagService.findAll();
}
@GetMapping("detail")
public Result findAllDetail(){
return tagService.findAllDetail();
}
@GetMapping("detail/{id}")
public Result findDetailById(@PathVariable("id") Long id){
return tagService.findDetailById(id);
}
}
TestController
package com.mszlu.blog.controller;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.utils.UserThreadLocal;
import com.mszlu.blog.vo.Result;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("test")
public class TestController {
@RequestMapping
public Result test(){
SysUser sysUser = UserThreadLocal.get();
System.out.println(sysUser);
return Result.success(null);
}
}
UploadController
package com.mszlu.blog.controller;
import com.mszlu.blog.utils.QiniuUtils;
import com.mszlu.blog.vo.Result;
import com.qiniu.common.QiniuException;
import org.apache.commons.lang3.StringUtils;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.UUID;
@RestController
@RequestMapping("upload")
public class UploadController {
@Autowired
private QiniuUtils qiniuUtils;
@PostMapping
public Result upload(@RequestParam("image") MultipartFile file){
//原始文件名称 比如 aa.png
String originalFilename = file.getOriginalFilename();
//唯一的文件名称
String fileName = UUID.randomUUID().toString() + "." + StringUtils.substringAfterLast(originalFilename, ".");
//上传文件 上传到哪呢? 七牛云 云服务器 按量付费 速度快 把图片发放到离用户最近的服务器上
// 降低 我们自身应用服务器的带宽消耗
boolean upload = qiniuUtils.upload(file, fileName);
if (upload){
return Result.success(QiniuUtils.url + fileName);
}
return Result.fail(20001,"上传失败");
}
public Result deleteFile(){
try {
qiniuUtils.deleteAll();
} catch (QiniuException e) {
e.printStackTrace();
}
return Result.success(null);
}
}
UsersController
package com.mszlu.blog.controller;
import com.mszlu.blog.service.SysUserService;
import com.mszlu.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("users")
public class UsersController {
@Autowired
private SysUserService sysUserService;
///users/currentUser
@GetMapping("currentUser")
public Result currentUser(@RequestHeader("Authorization") String token){
return sysUserService.findUserByToken(token);
}
}
dao
Archives
package com.mszlu.blog.dao.dos;
import lombok.Data;
@Data
public class Archives {
private Integer year;
private Integer month;
private Long count;
}
ArticleBodyMapper
package com.mszlu.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.dao.pojo.ArticleBody;
public interface ArticleBodyMapper extends BaseMapper<ArticleBody> {
}
ArticleMapper
package com.mszlu.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mszlu.blog.dao.dos.Archives;
import com.mszlu.blog.dao.pojo.Article;
import java.util.List;
public interface ArticleMapper extends BaseMapper<Article> {
List<Archives> listArchives();
IPage<Article> listArticle(Page<Article> page,
Long categoryId,
Long tagId,
String year,
String month);
}
ArticleTagMapper
package com.mszlu.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.dao.pojo.ArticleTag;
public interface ArticleTagMapper extends BaseMapper<ArticleTag> {
}
CategoryMapper
package com.mszlu.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.dao.pojo.Category;
public interface CategoryMapper extends BaseMapper<Category> {
}
CommentMapper
package com.mszlu.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.dao.pojo.Comment;
public interface CommentMapper extends BaseMapper<Comment> {
}
SysUserMapper
package com.mszlu.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.dao.pojo.SysUser;
public interface SysUserMapper extends BaseMapper<SysUser> {
}
TagMapper
package com.mszlu.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.dao.pojo.Tag;
import java.util.List;
public interface TagMapper extends BaseMapper<Tag> {
/**
* 根据文章id查询标签列表
* @param articleId
* @return
*/
List<Tag> findTagsByArticleId(Long articleId);
/**
* 查询最热的标签 前n条
* @param limit
* @return
*/
List<Long> findHotsTagIds(int limit);
List<Tag> findTagsByTagIds(List<Long> tagIds);
}
Article
package com.mszlu.blog.dao.pojo;
import lombok.Data;
@Data
public class Article {
public static final int Article_TOP = 1;
public static final int Article_Common = 0;
private Long id;
private String title;
private String summary;
private Integer commentCounts;
private Integer viewCounts;
/**
* 作者id
*/
private Long authorId;
/**
* 内容id
*/
private Long bodyId;
/**
*类别id
*/
private Long categoryId;
/**
* 置顶
*/
private Integer weight;
/**
* 创建时间
*/
private Long createDate;
}
ArticleBody
package com.mszlu.blog.dao.pojo;
import lombok.Data;
@Data
public class ArticleBody {
private Long id;
private String content;
private String contentHtml;
private Long articleId;
}
ArticleTag
package com.mszlu.blog.dao.pojo;
import lombok.Data;
@Data
public class ArticleTag {
private Long id;
private Long articleId;
private Long tagId;
}
Category
package com.mszlu.blog.dao.pojo;
import lombok.Data;
@Data
public class Category {
private Long id;
private String avatar;
private String categoryName;
private String description;
}
Comment
package com.mszlu.blog.dao.pojo;
import lombok.Data;
@Data
public class Comment {
private Long id;
private String content;
private Long createDate;
private Long articleId;
private Long authorId;
private Long parentId;
private Long toUid;
private Integer level;
}
SysUser
package com.mszlu.blog.dao.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
@Data
public class SysUser {
// @TableId(type = IdType.ASSIGN_ID) // 默认id类型
// 以后 用户多了之后,要进行分表操作,id就需要用分布式id了
// @TableId(type = IdType.AUTO) 数据库自增
private Long id;
private String account;
private Integer admin;
private String avatar;
private Long createDate;
private Integer deleted;
private String email;
private Long lastLogin;
private String mobilePhoneNumber;
private String nickname;
private String password;
private String salt;
private String status;
}
Tag
package com.mszlu.blog.dao.pojo;
import lombok.Data;
@Data
public class Tag {
private Long id;
private String avatar;
private String tagName;
}
handler
AllExceptionHandler
package com.mszlu.blog.handler;
import com.mszlu.blog.vo.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
//对加了@Controller注解的方法进行拦截处理 AOP的实现
@ControllerAdvice
public class AllExceptionHandler {
//进行异常处理,处理Exception.class的异常
@ExceptionHandler(Exception.class)
@ResponseBody //返回json数据
public Result doException(Exception ex){
ex.printStackTrace();
return Result.fail(-999,"系统异常");
}
}
LoginInterceptor
package com.mszlu.blog.handler;
import com.alibaba.fastjson.JSON;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.utils.UserThreadLocal;
import com.mszlu.blog.vo.ErrorCode;
import com.mszlu.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//在执行controller方法(Handler)之前进行执行
/**
* 1. 需要判断 请求的接口路径 是否为 HandlerMethod (controller方法)
* 2. 判断 token是否为空,如果为空 未登录
* 3. 如果token 不为空,登录验证 loginService checkToken
* 4. 如果认证成功 放行即可
*/
if (!(handler instanceof HandlerMethod)){
//handler 可能是 RequestResourceHandler springboot 程序 访问静态资源 默认去classpath下的static目录去查询
return true;
}
String token = request.getHeader("Authorization");
log.info("=================request start===========================");
String requestURI = request.getRequestURI();
log.info("request uri:{}",requestURI);
log.info("request method:{}",request.getMethod());
log.info("token:{}", token);
log.info("=================request end===========================");
if (StringUtils.isBlank(token)){
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(result));
return false;
}
SysUser sysUser = loginService.checkToken(token);
if (sysUser == null){
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(result));
return false;
}
//登录验证成功,放行
//我希望在controller中 直接获取用户的信息 怎么获取?
UserThreadLocal.put(sysUser);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//如果不删除 ThreadLocal中用完的信息 会有内存泄漏的风险
UserThreadLocal.remove();
}
}
ViewCountHandler
package com.mszlu.blog.handler;
import com.mszlu.blog.dao.mapper.ArticleMapper;
import com.mszlu.blog.dao.pojo.Article;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
@Component
@Slf4j
@EnableScheduling
public class ViewCountHandler {
@Autowired
private StringRedisTemplate redisTemplate;
@Resource
private ArticleMapper articleMapper;
@Scheduled(cron = "0/10 * * * * *")
//@Scheduled(cron = "0 0 3 * * *") 生产环境 每天凌晨3点执行
@Async("taskExecutor") //扔到线程池 执行
public void scheduled(){
log.info("=====>>>>> 同步浏览量开始执行 {}",new DateTime().toString("yyyy-MM-dd HH:mm:ss"));
Map<Object, Object> countMap = redisTemplate.opsForHash().entries("view_count");
for (Map.Entry<Object,Object> entry : countMap.entrySet()){
Long articleId = Long.parseLong(entry.getKey().toString());
Integer count = Integer.parseInt(entry.getValue().toString());
Article article = new Article();
article.setId(articleId);
article.setViewCounts(count);
articleMapper.updateById(article);
}
log.info("=====>>>>> 同步浏览量结束 {}",new DateTime().toString("yyyy-MM-dd HH:mm:ss"));
}
}
service
ArticleService
package com.mszlu.blog.service;
import com.mszlu.blog.vo.ArticleVo;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.ArticleParam;
import com.mszlu.blog.vo.params.PageParams;
public interface ArticleService {
/**
* 分页查询 文章列表
* @param pageParams
* @return
*/
Result listArticle(PageParams pageParams);
/**
* 最热文章
* @param limit
* @return
*/
Result hotArticle(int limit);
/**
* 最新文章
* @param limit
* @return
*/
Result newArticles(int limit);
/**
* 文章归档
* @return
*/
Result listArchives();
/**
* 查看文章详情
* @param articleId
* @return
*/
Result findArticleById(Long articleId);
/**
* 文章发布服务
* @param articleParam
* @return
*/
Result publish(ArticleParam articleParam);
/**
* 文章搜索
* @param search
* @return
*/
Result searchArticle(String search);
}
CategoryService
package com.mszlu.blog.service;
import com.mszlu.blog.vo.CategoryVo;
import com.mszlu.blog.vo.Result;
public interface CategoryService {
CategoryVo findCategoryById(Long categoryId);
Result findAll();
Result findAllDetail();
Result categoryDetailById(Long id);
}
CommentsService
package com.mszlu.blog.service;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.CommentParam;
public interface CommentsService {
/**
* 根据文章id 查询所有的评论列表
* @param id
* @return
*/
Result commentsByArticleId(Long id);
Result comment(CommentParam commentParam);
}
LoginService
package com.mszlu.blog.service;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.LoginParam;
import org.springframework.transaction.annotation.Transactional;
public interface LoginService {
/**
* 登录功能
* @param loginParam
* @return
*/
Result login(LoginParam loginParam);
SysUser checkToken(String token);
/**
* 退出登录
* @param token
* @return
*/
Result logout(String token);
/**
* 注册
* @param loginParam
* @return
*/
Result register(LoginParam loginParam);
}
SysUserService
package com.mszlu.blog.service;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.UserVo;
public interface SysUserService {
UserVo findUserVoById(Long id);
SysUser findUserById(Long id);
SysUser findUser(String account, String password);
/**
* 根据token查询用户信息
* @param token
* @return
*/
Result findUserByToken(String token);
/**
* 根据账户查找用户
* @param account
* @return
*/
SysUser findUserByAccount(String account);
/**
* 保存用户
* @param sysUser
*/
void save(SysUser sysUser);
}
TagService
package com.mszlu.blog.service;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.TagVo;
import java.util.List;
public interface TagService {
List<TagVo> findTagsByArticleId(Long articleId);
Result hots(int limit);
/**
* 查询所有的文章标签
* @return
*/
Result findAll();
Result findAllDetail();
Result findDetailById(Long id);
}
ThreadService
package com.mszlu.blog.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.mszlu.blog.dao.mapper.ArticleMapper;
import com.mszlu.blog.dao.pojo.Article;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
@Component
public class ThreadService {
@Resource
private ArticleMapper articleMapper;
@PostConstruct
public void initViewCount(){
//为了 保证 启动项目的时候,redis中的浏览量 如果没有,读取数据库的数据,进行初始化
//便于更新的时候 自增
List<Article> articles = articleMapper.selectList(new LambdaQueryWrapper<>());
for (Article article : articles) {
String viewCountStr = (String) redisTemplate.opsForHash().get("view_count", String.valueOf(article.getId()));
if (viewCountStr == null){
//初始化
redisTemplate.opsForHash().put("view_count", String.valueOf(article.getId()),String.valueOf(article.getViewCounts()));
}
}
}
@Autowired
private StringRedisTemplate redisTemplate;
//期望此操作在线程池 执行 不会影响原有的主线程
@Async("taskExecutor")
public void updateArticleViewCount(ArticleMapper articleMapper, Article article) {
// int viewCounts = article.getViewCounts();
// Article articleUpdate = new Article();
// articleUpdate.setViewCounts(viewCounts +1);
// LambdaUpdateWrapper<Article> updateWrapper = new LambdaUpdateWrapper<>();
// updateWrapper.eq(Article::getId,article.getId());
//设置一个 为了在多线程的环境下 线程安全
// updateWrapper.eq(Article::getViewCounts,viewCounts);
// update article set view_count=100 where view_count=99 and id=11
// articleMapper.update(articleUpdate,updateWrapper);
// try {
// Thread.sleep(5000);
// System.out.println("更新完成了....");
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//采用redis进行浏览量的增加
//hash结构 key 浏览量标识 field 文章id 后面1 表示自增加1
redisTemplate.opsForHash().increment("view_count",String.valueOf(article.getId()),1);
//定时任务在ViewCountHandler中
//还有一种方式是,redis自增之后,直接发送消息到消息队列中,由消息队列进行消费 来同步数据库,比定时任务要好一些
}
}
ArticleServiceImpl
package com.mszlu.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mszlu.blog.dao.dos.Archives;
import com.mszlu.blog.dao.mapper.ArticleBodyMapper;
import com.mszlu.blog.dao.mapper.ArticleMapper;
import com.mszlu.blog.dao.mapper.ArticleTagMapper;
import com.mszlu.blog.dao.pojo.Article;
import com.mszlu.blog.dao.pojo.ArticleBody;
import com.mszlu.blog.dao.pojo.ArticleTag;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.service.*;
import com.mszlu.blog.utils.UserThreadLocal;
import com.mszlu.blog.vo.*;
import com.mszlu.blog.vo.params.ArticleParam;
import com.mszlu.blog.vo.params.PageParams;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleMapper articleMapper;
@Autowired
private TagService tagService;
@Autowired
private SysUserService sysUserService;
@Autowired
private ArticleTagMapper articleTagMapper;
@Override
public Result listArticle(PageParams pageParams) {
Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());
IPage<Article> articleIPage = articleMapper.listArticle(
page,
pageParams.getCategoryId(),
pageParams.getTagId(),
pageParams.getYear(),
pageParams.getMonth());
List<Article> records = articleIPage.getRecords();
for (Article record : records) {
String viewCount = (String) redisTemplate.opsForHash().get("view_count", String.valueOf(record.getId()));
if (viewCount != null){
record.setViewCounts(Integer.parseInt(viewCount));
}
}
return Result.success(copyList(records,true,true));
}
// @Override
// public Result listArticle(PageParams pageParams) {
// /**
// * 1. 分页查询 article数据库表
// */
// Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());
// LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
// if (pageParams.getCategoryId() != null){
// // and category_id=#{categoryId}
// queryWrapper.eq(Article::getCategoryId,pageParams.getCategoryId());
// }
// List<Long> articleIdList = new ArrayList<>();
// if (pageParams.getTagId() != null){
// //加入标签 条件查询
// //article表中 并没有tag字段 一篇文章 有多个标签
// //article_tag article_id 1 : n tag_id
// LambdaQueryWrapper<ArticleTag> articleTagLambdaQueryWrapper = new LambdaQueryWrapper<>();
// articleTagLambdaQueryWrapper.eq(ArticleTag::getTagId,pageParams.getTagId());
// List<ArticleTag> articleTags = articleTagMapper.selectList(articleTagLambdaQueryWrapper);
// for (ArticleTag articleTag : articleTags) {
// articleIdList.add(articleTag.getArticleId());
// }
// if (articleIdList.size() > 0){
// // and id in(1,2,3)
// queryWrapper.in(Article::getId,articleIdList);
// }
// }
// //是否置顶进行排序
// //order by create_date desc
// queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate);
// Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);
// List<Article> records = articlePage.getRecords();
// //能直接返回吗? 很明显不能
// List<ArticleVo> articleVoList = copyList(records,true,true);
// return Result.success(articleVoList);
// }
@Override
public Result hotArticle(int limit) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByDesc(Article::getViewCounts);
queryWrapper.select(Article::getId,Article::getTitle);
queryWrapper.last("limit "+limit);
//select id,title from article order by view_counts desc limit 5
List<Article> articles = articleMapper.selectList(queryWrapper);
return Result.success(copyList(articles,false,false));
}
@Override
public Result newArticles(int limit) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByDesc(Article::getCreateDate);
queryWrapper.select(Article::getId,Article::getTitle);
queryWrapper.last("limit "+limit);
//select id,title from article order by create_date desc desc limit 5
List<Article> articles = articleMapper.selectList(queryWrapper);
return Result.success(copyList(articles,false,false));
}
@Override
public Result listArchives() {
List<Archives> archivesList = articleMapper.listArchives();
return Result.success(archivesList);
}
@Autowired
private ThreadService threadService;
@Autowired
private StringRedisTemplate redisTemplate;
// @Autowired
// private RocketMQTemplate rocketMQTemplate;
@Override
public Result findArticleById(Long articleId) {
/**
* 1. 根据id查询 文章信息
* 2. 根据bodyId和categoryid 去做关联查询
*/
Article article = this.articleMapper.selectById(articleId);
ArticleVo articleVo = copy(article, true, true,true,true);
//查看完文章了,新增阅读数,有没有问题呢?
//查看完文章之后,本应该直接返回数据了,这时候做了一个更新操作,更新时加写锁,阻塞其他的读操作,性能就会比较低
// 更新 增加了此次接口的 耗时 如果一旦更新出问题,不能影响 查看文章的操作
//线程池 可以把更新操作 扔到线程池中去执行,和主线程就不相关了
threadService.updateArticleViewCount(articleMapper,article);
String viewCount = (String) redisTemplate.opsForHash().get("view_count", String.valueOf(articleId));
if (viewCount != null){
articleVo.setViewCounts(Integer.parseInt(viewCount));
}
return Result.success(articleVo);
}
@Override
public Result publish(ArticleParam articleParam) {
//此接口 要加入到登录拦截当中
SysUser sysUser = UserThreadLocal.get();
/**
* 1. 发布文章 目的 构建Article对象
* 2. 作者id 当前的登录用户
* 3. 标签 要将标签加入到 关联列表当中
* 4. body 内容存储 article bodyId
*/
Article article = new Article();
boolean isEdit = false;
if (articleParam.getId() != null){
article = new Article();
article.setId(articleParam.getId());
article.setTitle(articleParam.getTitle());
article.setSummary(articleParam.getSummary());
article.setCategoryId(Long.parseLong(articleParam.getCategory().getId()));
articleMapper.updateById(article);
isEdit = true;
}else{
article = new Article();
article.setAuthorId(sysUser.getId());
article.setWeight(Article.Article_Common);
article.setViewCounts(0);
article.setTitle(articleParam.getTitle());
article.setSummary(articleParam.getSummary());
article.setCommentCounts(0);
article.setCreateDate(System.currentTimeMillis());
article.setCategoryId(Long.parseLong(articleParam.getCategory().getId()));
//插入之后 会生成一个文章id
this.articleMapper.insert(article);
}
//tag
List<TagVo> tags = articleParam.getTags();
if (tags != null){
for (TagVo tag : tags) {
Long articleId = article.getId();
if (isEdit){
//先删除
LambdaQueryWrapper<ArticleTag> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(ArticleTag::getArticleId,articleId);
articleTagMapper.delete(queryWrapper);
}
ArticleTag articleTag = new ArticleTag();
articleTag.setTagId(Long.parseLong(tag.getId()));
articleTag.setArticleId(articleId);
articleTagMapper.insert(articleTag);
}
}
//body
if (isEdit){
ArticleBody articleBody = new ArticleBody();
articleBody.setArticleId(article.getId());
articleBody.setContent(articleParam.getBody().getContent());
articleBody.setContentHtml(articleParam.getBody().getContentHtml());
LambdaUpdateWrapper<ArticleBody> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.eq(ArticleBody::getArticleId,article.getId());
articleBodyMapper.update(articleBody, updateWrapper);
}else {
ArticleBody articleBody = new ArticleBody();
articleBody.setArticleId(article.getId());
articleBody.setContent(articleParam.getBody().getContent());
articleBody.setContentHtml(articleParam.getBody().getContentHtml());
articleBodyMapper.insert(articleBody);
article.setBodyId(articleBody.getId());
articleMapper.updateById(article);
}
Map<String,String> map = new HashMap<>();
map.put("id",article.getId().toString());
if (isEdit){
//发送一条消息给rocketmq 当前文章更新了,更新一下缓存吧
ArticleMessage articleMessage = new ArticleMessage();
articleMessage.setArticleId(article.getId());
// rocketMQTemplate.convertAndSend("blog-update-article",articleMessage);
}
return Result.success(map);
}
@Override
public Result searchArticle(String search) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByDesc(Article::getViewCounts);
queryWrapper.select(Article::getId,Article::getTitle);
queryWrapper.like(Article::getTitle,search);
//select id,title from article order by view_counts desc limit 5
List<Article> articles = articleMapper.selectList(queryWrapper);
return Result.success(copyList(articles,false,false));
}
private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article record : records) {
articleVoList.add(copy(record,isTag,isAuthor,false,false));
}
return articleVoList;
}
private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor, boolean isBody,boolean isCategory) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article record : records) {
articleVoList.add(copy(record,isTag,isAuthor,isBody,isCategory));
}
return articleVoList;
}
@Autowired
private CategoryService categoryService;
private ArticleVo copy(Article article, boolean isTag, boolean isAuthor, boolean isBody,boolean isCategory){
ArticleVo articleVo = new ArticleVo();
articleVo.setId(String.valueOf(article.getId()));
BeanUtils.copyProperties(article,articleVo);
articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
//并不是所有的接口 都需要标签 ,作者信息
if (isTag){
Long articleId = article.getId();
articleVo.setTags(tagService.findTagsByArticleId(articleId));
}
if (isAuthor){
Long authorId = article.getAuthorId();
SysUser sysUser = sysUserService.findUserById(authorId);
UserVo userVo = new UserVo();
userVo.setAvatar(sysUser.getAvatar());
userVo.setId(sysUser.getId().toString());
userVo.setNickname(sysUser.getNickname());
articleVo.setAuthor(userVo);
}
if (isBody){
Long bodyId = article.getBodyId();
articleVo.setBody(findArticleBodyById(bodyId));
}
if (isCategory){
Long categoryId = article.getCategoryId();
articleVo.setCategory(categoryService.findCategoryById(categoryId));
}
return articleVo;
}
@Autowired
private ArticleBodyMapper articleBodyMapper;
private ArticleBodyVo findArticleBodyById(Long bodyId) {
ArticleBody articleBody = articleBodyMapper.selectById(bodyId);
ArticleBodyVo articleBodyVo = new ArticleBodyVo();
articleBodyVo.setContent(articleBody.getContent());
return articleBodyVo;
}
}
CategoryServiceImpl
package com.mszlu.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.blog.dao.mapper.CategoryMapper;
import com.mszlu.blog.dao.pojo.Category;
import com.mszlu.blog.service.CategoryService;
import com.mszlu.blog.vo.CategoryVo;
import com.mszlu.blog.vo.Result;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Override
public CategoryVo findCategoryById(Long categoryId) {
Category category = categoryMapper.selectById(categoryId);
CategoryVo categoryVo = new CategoryVo();
BeanUtils.copyProperties(category,categoryVo);
categoryVo.setId(String.valueOf(category.getId()));
return categoryVo;
}
@Override
public Result findAll() {
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.select(Category::getId,Category::getCategoryName);
List<Category> categories = categoryMapper.selectList(queryWrapper);
//页面交互的对象
return Result.success(copyList(categories));
}
@Override
public Result findAllDetail() {
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
List<Category> categories = categoryMapper.selectList(queryWrapper);
//页面交互的对象
return Result.success(copyList(categories));
}
@Override
public Result categoryDetailById(Long id) {
Category category = categoryMapper.selectById(id);
return Result.success(copy(category));
}
public CategoryVo copy(Category category){
CategoryVo categoryVo = new CategoryVo();
BeanUtils.copyProperties(category,categoryVo);
categoryVo.setId(String.valueOf(category.getId()));
return categoryVo;
}
public List<CategoryVo> copyList(List<Category> categoryList){
List<CategoryVo> categoryVoList = new ArrayList<>();
for (Category category : categoryList) {
categoryVoList.add(copy(category));
}
return categoryVoList;
}
}
CommentsServiceImpl
package com.mszlu.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.mszlu.blog.dao.mapper.ArticleMapper;
import com.mszlu.blog.dao.mapper.CommentMapper;
import com.mszlu.blog.dao.pojo.Article;
import com.mszlu.blog.dao.pojo.Comment;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.service.CommentsService;
import com.mszlu.blog.service.SysUserService;
import com.mszlu.blog.utils.UserThreadLocal;
import com.mszlu.blog.vo.CommentVo;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.UserVo;
import com.mszlu.blog.vo.params.CommentParam;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class CommentsServiceImpl implements CommentsService {
@Autowired
private CommentMapper commentMapper;
@Autowired
private SysUserService sysUserService;
@Autowired
private ArticleMapper articleMapper;
@Override
public Result commentsByArticleId(Long id) {
/**
* 1. 根据文章id 查询 评论列表 从 comment 表中查询
* 2. 根据作者的id 查询作者的信息
* 3. 判断 如果 level = 1 要去查询它有没有子评论
* 4. 如果有 根据评论id 进行查询 (parent_id)
*/
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getArticleId,id);
queryWrapper.eq(Comment::getLevel,1);
queryWrapper.orderByDesc(Comment::getCreateDate);
List<Comment> comments = commentMapper.selectList(queryWrapper);
List<CommentVo> commentVoList = copyList(comments);
return Result.success(commentVoList);
}
@Override
public Result comment(CommentParam commentParam) {
SysUser sysUser = UserThreadLocal.get();
Comment comment = new Comment();
comment.setArticleId(commentParam.getArticleId());
comment.setAuthorId(sysUser.getId());
comment.setContent(commentParam.getContent());
comment.setCreateDate(System.currentTimeMillis());
Long parent = commentParam.getParent();
if (parent == null || parent == 0) {
comment.setLevel(1);
}else{
comment.setLevel(2);
}
comment.setParentId(parent == null ? 0 : parent);
Long toUserId = commentParam.getToUserId();
comment.setToUid(toUserId == null ? 0 : toUserId);
this.commentMapper.insert(comment);
UpdateWrapper<Article> updateWrapper = Wrappers.update();
updateWrapper.eq("id",comment.getArticleId());
updateWrapper.setSql(true,"comment_counts=comment_counts+1");
this.articleMapper.update(null,updateWrapper);
return Result.success(null);
}
private List<CommentVo> copyList(List<Comment> comments) {
List<CommentVo> commentVoList = new ArrayList<>();
for (Comment comment : comments) {
commentVoList.add(copy(comment));
}
return commentVoList;
}
private CommentVo copy(Comment comment) {
CommentVo commentVo = new CommentVo();
BeanUtils.copyProperties(comment,commentVo);
commentVo.setId(String.valueOf(comment.getId()));
//作者信息
Long authorId = comment.getAuthorId();
UserVo userVo = this.sysUserService.findUserVoById(authorId);
commentVo.setAuthor(userVo);
//子评论
Integer level = comment.getLevel();
if (1 == level){
Long id = comment.getId();
List<CommentVo> commentVoList = findCommentsByParentId(id);
commentVo.setChildrens(commentVoList);
}
//to User 给谁评论
if (level > 1){
Long toUid = comment.getToUid();
UserVo toUserVo = this.sysUserService.findUserVoById(toUid);
commentVo.setToUser(toUserVo);
}
return commentVo;
}
private List<CommentVo> findCommentsByParentId(Long id) {
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getParentId,id);
queryWrapper.eq(Comment::getLevel,2);
return copyList(commentMapper.selectList(queryWrapper));
}
}
LoginServiceImpl
package com.mszlu.blog.service.impl;
import com.alibaba.fastjson.JSON;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.service.SysUserService;
import com.mszlu.blog.utils.JWTUtils;
import com.mszlu.blog.vo.ErrorCode;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.LoginParam;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
@Transactional
public class LoginServiceImpl implements LoginService {
@Autowired
private SysUserService sysUserService;
@Autowired
private RedisTemplate<String,String> redisTemplate;
private static final String slat = "mszlu!@#";
@Override
public Result login(LoginParam loginParam) {
/**
* 1. 检查参数是否合法
* 2. 根据用户名和密码去user表中查询 是否存在
* 3. 如果不存在 登录失败
* 4. 如果存在 ,使用jwt 生成token 返回给前端
* 5. token放入redis当中,redis token:user信息 设置过期时间
* (登录认证的时候 先认证token字符串是否合法,去redis认证是否存在)
*/
String account = loginParam.getAccount();
String password = loginParam.getPassword();
if (StringUtils.isBlank(account) || StringUtils.isBlank(password)){
return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
}
password = DigestUtils.md5Hex(password + slat);
SysUser sysUser = sysUserService.findUser(account,password);
if (sysUser == null){
return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(),ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());
}
String token = JWTUtils.createToken(sysUser.getId());
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
return Result.success(token);
}
@Override
public SysUser checkToken(String token) {
if (StringUtils.isBlank(token)){
return null;
}
Map<String, Object> stringObjectMap = JWTUtils.checkToken(token);
if (stringObjectMap == null){
return null;
}
String userJson = redisTemplate.opsForValue().get("TOKEN_" + token);
if (StringUtils.isBlank(userJson)){
return null;
}
SysUser sysUser = JSON.parseObject(userJson, SysUser.class);
return sysUser;
}
@Override
public Result logout(String token) {
redisTemplate.delete("TOKEN_"+token);
return Result.success(null);
}
@Override
public Result register(LoginParam loginParam) {
/**
* 1. 判断参数 是否合法
* 2. 判断账户是否存在,存在 返回账户已经被注册
* 3. 不存在,注册用户
* 4. 生成token
* 5. 存入redis 并返回
* 6. 注意 加上事务,一旦中间的任何过程出现问题,注册的用户 需要回滚
*/
String account = loginParam.getAccount();
String password = loginParam.getPassword();
String nickname = loginParam.getNickname();
if (StringUtils.isBlank(account)
|| StringUtils.isBlank(password)
|| StringUtils.isBlank(nickname)
){
return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
}
SysUser sysUser = sysUserService.findUserByAccount(account);
if (sysUser != null){
return Result.fail(ErrorCode.ACCOUNT_EXIST.getCode(),"账户已经被注册了");
}
sysUser = new SysUser();
sysUser.setNickname(nickname);
sysUser.setAccount(account);
sysUser.setPassword(DigestUtils.md5Hex(password+slat));
sysUser.setCreateDate(System.currentTimeMillis());
sysUser.setLastLogin(System.currentTimeMillis());
sysUser.setAvatar("/static/img/logo.b3a48c0.png");
sysUser.setAdmin(1); //1 为true
sysUser.setDeleted(0); // 0 为false
sysUser.setSalt("");
sysUser.setStatus("");
sysUser.setEmail("");
this.sysUserService.save(sysUser);
String token = JWTUtils.createToken(sysUser.getId());
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
return Result.success(token);
}
}
SysUserServiceImpl
package com.mszlu.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.blog.dao.mapper.SysUserMapper;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.service.SysUserService;
import com.mszlu.blog.vo.ErrorCode;
import com.mszlu.blog.vo.LoginUserVo;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.UserVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class SysUserServiceImpl implements SysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Autowired
private LoginService loginService;
@Override
public UserVo findUserVoById(Long id) {
SysUser sysUser = sysUserMapper.selectById(id);
if (sysUser == null){
sysUser = new SysUser();
sysUser.setId(1L);
sysUser.setAvatar("/static/img/logo.b3a48c0.png");
sysUser.setNickname("码神之路");
}
UserVo userVo = new UserVo();
BeanUtils.copyProperties(sysUser,userVo);
userVo.setId(String.valueOf(sysUser.getId()));
return userVo;
}
@Override
public SysUser findUserById(Long id) {
SysUser sysUser = sysUserMapper.selectById(id);
if (sysUser == null){
sysUser = new SysUser();
sysUser.setNickname("码神之路");
}
return sysUser;
}
@Override
public SysUser findUser(String account, String password) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getAccount,account);
queryWrapper.eq(SysUser::getPassword,password);
queryWrapper.select(SysUser::getAccount,SysUser::getId,SysUser::getAvatar,SysUser::getNickname);
queryWrapper.last("limit 1");
return sysUserMapper.selectOne(queryWrapper);
}
@Override
public Result findUserByToken(String token) {
/**
* 1. token合法性校验
* 是否为空,解析是否成功 redis是否存在
* 2. 如果校验失败 返回错误
* 3. 如果成功,返回对应的结果 LoginUserVo
*/
SysUser sysUser = loginService.checkToken(token);
if (sysUser == null){
return Result.fail(ErrorCode.TOKEN_ERROR.getCode(),ErrorCode.TOKEN_ERROR.getMsg());
}
LoginUserVo loginUserVo = new LoginUserVo();
loginUserVo.setId(String.valueOf(sysUser.getId()));
loginUserVo.setNickname(sysUser.getNickname());
loginUserVo.setAvatar(sysUser.getAvatar());
loginUserVo.setAccount(sysUser.getAccount());
return Result.success(loginUserVo);
}
@Override
public SysUser findUserByAccount(String account) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getAccount,account);
queryWrapper.last("limit 1");
return this.sysUserMapper.selectOne(queryWrapper);
}
@Override
public void save(SysUser sysUser) {
//保存用户这 id会自动生成
//这个地方 默认生成的id是 分布式id 雪花算法
//mybatis-plus
this.sysUserMapper.insert(sysUser);
}
}
TagServiceImpl
package com.mszlu.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.blog.dao.mapper.TagMapper;
import com.mszlu.blog.dao.pojo.Tag;
import com.mszlu.blog.service.TagService;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.TagVo;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@Service
public class TagServiceImpl implements TagService {
@Autowired
private TagMapper tagMapper;
public TagVo copy(Tag tag){
TagVo tagVo = new TagVo();
BeanUtils.copyProperties(tag,tagVo);
tagVo.setId(String.valueOf(tag.getId()));
return tagVo;
}
public List<TagVo> copyList(List<Tag> tagList){
List<TagVo> tagVoList = new ArrayList<>();
for (Tag tag : tagList) {
tagVoList.add(copy(tag));
}
return tagVoList;
}
@Override
public List<TagVo> findTagsByArticleId(Long articleId) {
//mybatisplus 无法进行多表查询
List<Tag> tags = tagMapper.findTagsByArticleId(articleId);
return copyList(tags);
}
@Override
public Result hots(int limit) {
/**
* 1. 标签所拥有的文章数量最多 最热标签
* 2. 查询 根据tag_id 分组 计数,从大到小 排列 取前 limit个
*/
List<Long> tagIds = tagMapper.findHotsTagIds(limit);
if (CollectionUtils.isEmpty(tagIds)){
return Result.success(Collections.emptyList());
}
//需求的是 tagId 和 tagName Tag对象
//select * from tag where id in (1,2,3,4)
List<Tag> tagList = tagMapper.findTagsByTagIds(tagIds);
return Result.success(tagList);
}
@Override
public Result findAll() {
LambdaQueryWrapper<Tag> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.select(Tag::getId,Tag::getTagName);
List<Tag> tags = this.tagMapper.selectList(queryWrapper);
return Result.success(copyList(tags));
}
@Override
public Result findAllDetail() {
LambdaQueryWrapper<Tag> queryWrapper = new LambdaQueryWrapper<>();
List<Tag> tags = this.tagMapper.selectList(queryWrapper);
return Result.success(copyList(tags));
}
@Override
public Result findDetailById(Long id) {
Tag tag = tagMapper.selectById(id);
return Result.success(copy(tag));
}
}
utils
HttpContextUtils
package com.mszlu.blog.utils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* HttpServletRequest
*
*/
public class HttpContextUtils {
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
}
IpUtils
package com.mszlu.blog.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* 获取Ip
*
*/
@Slf4j
public class IpUtils {
/**
* 获取IP地址
* <p>
* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = null, unknown = "unknown", seperator = ",";
int maxLength = 15;
try {
ip = request.getHeader("x-forwarded-for");
if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} catch (Exception e) {
log.error("IpUtils ERROR ", e);
}
// 使用代理,则获取第一个IP地址
if (StringUtils.isEmpty(ip) && ip.length() > maxLength) {
int idx = ip.indexOf(seperator);
if (idx > 0) {
ip = ip.substring(0, idx);
}
}
return ip;
}
/**
* 获取ip地址
*
* @return
*/
public static String getIpAddr() {
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
return getIpAddr(request);
}
}
JWTUtils
package com.mszlu.blog.utils;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.jasypt.util.text.BasicTextEncryptor;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JWTUtils {
private static final String jwtToken = "123456Mszlu!@#$$";
public static String createToken(Long userId){
Map<String,Object> claims = new HashMap<>();
claims.put("userId",userId);
JwtBuilder jwtBuilder = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,秘钥为jwtToken
.setClaims(claims) // body数据,要唯一,自行设置
.setIssuedAt(new Date()) // 设置签发时间
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 60 * 1000));// 一天的有效时间
String token = jwtBuilder.compact();
return token;
}
public static Map<String, Object> checkToken(String token){
try {
Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
return (Map<String, Object>) parse.getBody();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
//加密所需的salt
textEncryptor.setPassword("mszlu_blog_$#@wzb_&^%$#");
//要加密的数据(数据库的用户名或密码)
String username = textEncryptor.encrypt("root");
String password = textEncryptor.encrypt("root");
System.out.println("username:"+username);
System.out.println("password:"+password);
System.out.println(textEncryptor.decrypt("29cZ+X9cNmECjbLXT2P/BBZWReVl30NS"));
}
}
QiniuUtils
package com.mszlu.blog.utils;
import com.alibaba.fastjson.JSON;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.storage.model.FileInfo;
import com.qiniu.storage.model.FileListing;
import com.qiniu.util.Auth;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@Component
public class QiniuUtils {
public static final String url = "https://static.mszlu.com/";
@Value("${qiniu.accessKey}")
private String accessKey;
@Value("${qiniu.accessSecretKey}")
private String accessSecretKey;
public boolean upload(MultipartFile file,String fileName){
//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.huabei());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传
String bucket = "mszlu";
//默认不指定key的情况下,以文件内容的hash值作为文件名
try {
byte[] uploadBytes = file.getBytes();
Auth auth = Auth.create(accessKey, accessSecretKey);
String upToken = auth.uploadToken(bucket);
Response response = uploadManager.put(uploadBytes, fileName, upToken);
//解析上传成功的结果
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
return true;
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
public void deleteAll() throws QiniuException {
String bucket = "wwwmszlucom";
Auth auth = Auth.create(accessKey, accessSecretKey);
//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.huabei());
BucketManager bucketManager = new BucketManager(auth,cfg);
FileListing fileListing = bucketManager.listFiles(bucket, "/", "", 1000, "");
FileInfo[] items = fileListing.items;
for (FileInfo info : items){
}
}
}
UserThreadLocal
package com.mszlu.blog.utils;
import com.mszlu.blog.dao.pojo.SysUser;
public class UserThreadLocal {
private UserThreadLocal(){}
//线程变量隔离
private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();
public static void put(SysUser sysUser){
LOCAL.set(sysUser);
}
public static SysUser get(){
return LOCAL.get();
}
public static void remove(){
LOCAL.remove();
}
}
vo
Result
package com.mszlu.blog.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Result {
private boolean success;
private int code;
private String msg;
private Object data;
public static Result success(Object data){
return new Result(true,200,"success",data);
}
public static Result fail(int code, String msg){
return new Result(false,code,msg,null);
}
}
LoginUserVo
package com.mszlu.blog.vo;
import lombok.Data;
@Data
public class LoginUserVo {
private String id;
private String account;
private String nickname;
private String avatar;
}
ErrorCode
package com.mszlu.blog.vo;
public enum ErrorCode {
PARAMS_ERROR(10001,"参数有误"),
ACCOUNT_PWD_NOT_EXIST(10002,"用户名或密码不存在"),
TOKEN_ERROR(10003,"token不合法"),
ACCOUNT_EXIST(10004,"账号已存在"),
NO_PERMISSION(70001,"无访问权限"),
SESSION_TIME_OUT(90001,"会话超时"),
NO_LOGIN(90002,"未登录"),;
private int code;
private String msg;
ErrorCode(int code, String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
application.properties
server.port=8888
spring.application.name=mszlu_blog
#rocketmq\u914D\u7F6E
#rocketmq.name-server=192.168.200.100:9876
#rocketmq.producer.group=blog_group
# datasource \u5BC6\u7801 root root
spring.datasource.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.datasource.username=ENC(erpLZDP2WfoEP+Enyq9iAA==)
spring.datasource.password=ENC(ZdqPTlBXsD6U982Ldeh9mg==)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.profiles.active=prod
#mybatis-plus
#mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.table-prefix=ms_
spring.redis.host=localhost
spring.redis.port=6379
spring.servlet.multipart.max-request-size=20MB
spring.servlet.multipart.max-file-size=2MB
jasypt.encryptor.password=mszlu_blog_$#@wzb_&^%$#
#\uFFFD\uFFFD\u0163\uFFFD\uFFFD\uFFFD\uFFFD
qiniu.accessKey=11
qiniu.accessSecretKey=22
ArticleMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis配置文件-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mszlu.blog.dao.mapper.ArticleMapper">
<resultMap id="articleMap" type="com.mszlu.blog.dao.pojo.Article">
<id column="id" property="id" />
<result column="author_id" property="authorId"/>
<result column="comment_counts" property="commentCounts"/>
<result column="create_date" property="createDate"/>
<result column="summary" property="summary"/>
<result column="title" property="title"/>
<result column="view_counts" property="viewCounts"/>
<result column="weight" property="weight"/>
<result column="body_id" property="bodyId"/>
<result column="category_id" property="categoryId"/>
</resultMap>
<select id="listArchives" resultType="com.mszlu.blog.dao.dos.Archives">
select FROM_UNIXTIME(create_date/1000,'%Y') as year,FROM_UNIXTIME(create_date/1000,'%m') as month,count(*) as count from ms_article group by year,month
</select>
<!-- Long categoryId,
Long tagId,
String year,
String month-->
<select id="listArticle" resultMap="articleMap">
select * from ms_article
<where>
1 = 1
<if test="categoryId != null">
and category_id=#{categoryId}
</if>
<if test="tagId != null">
and id in (select article_id from ms_article_tag where tag_id=#{tagId})
</if>
<if test="year != null and year.length>0 and month != null and month.length>0">
and (FROM_UNIXTIME(create_date/1000,'%Y') =#{year} and FROM_UNIXTIME(create_date/1000,'%m')=#{month})
</if>
</where>
order by weight,create_date desc
</select>
</mapper>
#### TagMapper.xml
<!-- List<Tag> findTagsByArticleId(Long articleId);-->
<select id="findTagsByArticleId" parameterType="long" resultType="com.mszlu.blog.dao.pojo.Tag">
select id,avatar,tag_name as tagName from ms_tag
where id in
(select tag_id from ms_article_tag where article_id=#{articleId})
</select>
<!-- List<Long> findHotsTagIds(int limit);-->
<select id="findHotsTagIds" parameterType="int" resultType="java.lang.Long">
SELECT tag_id FROM `ms_article_tag` group by tag_id order by count(*) desc limit #{limit}
</select>
<!-- List<Tag> findTagsByTagIds(List<Long> tagIds);-->
<select id="findTagsByTagIds" parameterType="list" resultType="com.mszlu.blog.dao.pojo.Tag">
select id,tag_name as tagName from ms_tag
where id in
<foreach collection="tagIds" item="tagId" separator="," open="(" close=")">
#{tagId}
</foreach>
</select>

码神之路博客项目
浙公网安备 33010602011771号