Springboot+Vue博客项目总结
分类专栏: 项目 文章标签: java 微服务 springboot vue 项目
版权
项目 专栏收录该内容
1 篇文章 0 订阅
订阅专栏
文章目录
- Springboot+Vue博客项目总结
-
- 1.工程搭建
-
- 1.1 新建maven工程
- 1.2 application.properties配置
- 1.3 配置分页插件
- 1.4 配置解决跨域
- 1.5 添加启动类
- 2.统一异常处理
- 3.登录功能实现
-
- 3.1 接口说明
- 3.2 JWT
- 3.3 Controller
- 3.4 Service
- 3.5 登录参数,redis配置
- 5.获取用户信息
-
- 5.1 接口说明
- 5.2 Controller
- 5.3 Service
- 6.登录拦截器
-
- 7.ThreadLocal保存用户信息
- 8. 使用线程池更新阅读次数
-
- 9.评论
-
- 9.1 接口说明
- 9.2 加入到登录拦截器中
- 9.3 Controller
- 9.4 Service
- 10.AOP统一记录日志
- 11.文章图片上传
-
- 11.1 接口说明
- 11.2 Controller
- 11.3 使用七牛云
- 12.AOP实习统一缓存处理(优化)
- 13.年月归档中MySQL查询
-
- 13.1 Controller
- 13.2 Service
- 13.3 具体sql实现
- 14.对后端进行返回统一的标准格式
-
- 15.项目亮点总结
Springboot+Vue博客项目总结
1.工程搭建
1.1 新建maven工程
<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.0modelVersion>
<groupId>com.mszlu</groupId>
<artifactId>blog-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<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>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<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>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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
project>
1.2 application.properties配置
#server
server.port= 8888
spring.application.name=mszlu_blog
# datasource
spring.datasource.url=jdbc:mysql://localhost:3306/blogxpp?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
#定义前缀表名,因为数据库中的表带ms_。这样实体类的表不用加前缀就可以匹配
mybatis-plus.global-config.db-config.table-prefix=ms_
1.3 配置分页插件
不知道的可以查看MyBatis-Plus官网关于分页插件的介绍
@Configuration
@MapperScan("com.xpp.blog.dao.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
1.4 配置解决跨域
解决跨域问题可以参考:SpringBoot解决跨域的5种方式
前后端端口不一样,需要解决跨域问题。
这里解决的方法是重写WebMvcConfigurer
:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8080");
}
}
1.5 添加启动类
@SpringBootApplication
public class BlogApp {
public static void main(String[] args) {
SpringApplication.run(BlogApp.class,args);
}
}
2.统一异常处理
不管是controller
层还是service
,dao
层,都有可能报异常,如果是预料中的异常,可以直接捕获处理,如果是意料之外的异常,需要统一进行处理,进行记录,并给用户提示相对比较友好的信息。
@ControllerAdvice
:对加了@Controller
的方法进行拦截处理,AOP的实现
@ExceptionHandler
:统一处理某一类异常,从而减少代码重复率和复杂度,比如要获取自定义异常可以@ExceptionHandler(BusinessException.class)
@ControllerAdvice
public class AllExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result doException(Exception e){
e.printStackTrace();
return Result.fail(-999,"系统异常");
}
}
3.登录功能实现
3.1 接口说明
接口url:/login
请求方式:POST
请求参数:
参数名称 |
参数类型 |
说明 |
account |
string |
账号 |
password |
string |
密码 |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": "token"
}
3.2 JWT
可以参考:JWT整合Springboot
登录使用JWT
技术:
- jwt 可以生成 一个加密的
token
,做为用户登录的令牌,当用户登录成功之后,发放给客户端。
- 请求需要登录的资源或者接口的时候,将
token
携带,后端验证token
是否合法。
jwt 有三部分组成:A.B.C
- A:
Header
,{“type”:“JWT”,“alg”:“HS256”} 固定
- B:
playload
,存放信息,比如,用户id,过期时间等等,可以被解密,不能存放敏感信息
- C:
签证
,A和B加上秘钥加密而成,只要秘钥不丢失,可以认为是安全的。
jwt 验证,主要就是验证C部分是否合法。
依赖包:
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
工具类:
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)
.setClaims(claims)
.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) {
String token=JWTUtils.createToken(100L);
System.out.println(token);
Map<String, Object> map = JWTUtils.checkToken(token);
System.out.println(map.get("userId"));
}
}
3.3 Controller
@RestController
@RequestMapping("login")
public class loginController {
@Autowired
private LoginService loginService;
@PostMapping
public Result login(@RequestBody LoginParam loginParam){
return loginService.login(loginParam);
}
}
3.4 Service
关于这里StringUtils的用法:Java之StringUtils的常用方法
md5加密的依赖包:
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
dependency>
@Service
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) {
String account = loginParam.getAccount();
String password = loginParam.getPassword();
if (StringUtils.isBlank(account) || StringUtils.isAllBlank(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 findUser(String account, String password) {
LambdaQueryWrapper<SysUser> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getAccount,account);
queryWrapper.eq(SysUser::getPassword,password);
queryWrapper.select(SysUser::getId,SysUser::getAccount,SysUser::getAvatar,SysUser::getNickname);
queryWrapper.last("limit 1");
SysUser sysUser = sysUserMapper.selectOne(queryWrapper);
return sysUser;
}
3.5 登录参数,redis配置
接受前端传来的登录参数:
@Data
public class LoginParam {
private String account;
private String password;
}
配置redis:
spring.redis.host=localhost
spring.redis.port=6379
5.获取用户信息
5.1 接口说明
接口url:/users/currentUser
请求方式:GET
请求参数:
参数名称 |
参数类型 |
说明 |
Authorization |
string |
头部信息(TOKEN) |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": {
"id":1,
"account":"1",
"nickaname":"1",
"avatar":"ss"
}
}
5.2 Controller
@RestController
@RequestMapping("users")
public class UserController {
@Autowired
private SysUserService sysUserService;
@GetMapping("currentUser")
public Result currentUser(@RequestHeader("Authorization") String token){
return sysUserService.findUserByToken(token);
}
}
5.3 Service
@Override
public Result findUserByToken(String token) {
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.setAccount(sysUser.getAccount());
loginUserVo.setAvatar(sysUser.getAvatar());
return Result.success(loginUserVo);
}
@Override
public SysUser checkToken(String token) {
if (StringUtils.isAllBlank(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;
}
6.登录拦截器
每次访问需要登录的资源的时候,都需要在代码中进行判断,一旦登录的逻辑有所改变,代码都得进行变动,非常不合适。
那么可不可以统一进行登录判断呢?
可以,使用拦截器,进行登录拦截,如果遇到需要登录才能访问的接口,如果未登录,拦截器直接返回,并跳转登录页面。
6.1 拦截器实现
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
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;
}
UserThreadLocal.put(sysUser);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserThreadLocal.remove();
}
}
6.2 使拦截器生效
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8080");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/test");
}
}
测试:
@RestController
@RequestMapping("test")
public class TestController {
@RequestMapping
public Result test(){
return Result.success(null);
}
}
7.ThreadLocal保存用户信息
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();
}
}
8. 使用线程池更新阅读次数
可以参考:在SpringBoot中实现异步事件驱动
8.1 线程池配置
@ControllerAdvice
@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;
}
}
8.2 使用
@Autowired
private ThreadService threadService;
-
查看文章详情
-
@param articleId
-
@return
*/
@Override
public Result findArticleById(Long articleId) {
Article article = articleMapper.selectById(articleId);
ArticleVo articleVo = copy(article, true, true, true, true);
threadService.updateArticleViewCount(articleMapper, article);
return Result.success(articleVo);
}
@Component
public class ThreadService {
@Async("taskExecutor")
public void updateArticleViewCount(ArticleMapper articleMapper, Article article) {
Article articleUpdate = new Article();
int viewCounts = article.getViewCounts();
articleUpdate.setViewCounts(viewCounts + 1);
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Article::getId, article.getId());
queryWrapper.eq(Article::getViewCounts, viewCounts);
articleMapper.update(articleUpdate, queryWrapper);
try {
Thread.sleep(5000);
System.out.println("更新完成了!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这里的update
用法:
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
参数说明:
类型 |
参数名 |
描述 |
T |
entity |
实体对象 (set 条件值,可为 null) |
Wrapper |
updateWrapper |
实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) |
@Async
注解:如果我们想在调用一个方法的时候开启一个新的线程开始异步操作,我们只需要在这个方法上加上@Async
注解,当然前提是,这个方法所在的类必须在Spring环境中。
9.评论
9.1 接口说明
接口url:/comments/create/change
请求方式:POST
请求参数:
参数名称 |
参数类型 |
说明 |
articleId |
long |
文章id |
content |
string |
评论内容 |
parent |
long |
父评论id |
toUserId |
long |
被评论的用户id |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": null
}
9.2 加入到登录拦截器中
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/test").addPathPatterns("/comments/create/change");
}
9.3 Controller
构建评论参数对象:
package com.mszlu.blog.vo.params;
import lombok.Data;
@Data
public class CommentParam {
private Long articleId;
private String content;
private Long parent;
private Long toUserId;
}
@PostMapping("create/change")
public Result comment(@RequestBody CommentParam commentParam){
return commentsService.comment(commentParam);
}
9.4 Service
@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);
commentMapper.insert(comment);
return Result.success(null);
}
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
10.AOP统一记录日志
关于AOP的文章可以参考:
- Spring-AOP基础概念和操作详解
- SpringBoot开发秘籍 - 利用 AOP 记录日志
自己实现一个日志注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
String module() default "";
String operator() default "";
}
统一日志处理切面
@Component
@Aspect
@Slf4j
public class LogAspect {
@Pointcut("@annotation(com.xpp.blog.common.aop.LogAnnotation)")
public void pt(){
}
@Around("pt()")
public Object log(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
Object result = point.proceed();
long time=System.currentTimeMillis()-beginTime;
recordLog(point,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);
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
log.info("ip:{}", IpUtils.getIpAddr(request));
log.info("excute time : {} ms",time);
log.info("=====================log end================================");
}
}
使用
@PostMapping("")
@LogAnnotation(module = "文章", operator = "获取文章列表")
public Result listArticles(@RequestBody PageParams params) {
return articleService.listArticle(params);
}
11.文章图片上传
11.1 接口说明
接口url:/upload
请求方式:POST
请求参数:
参数名称 |
参数类型 |
说明 |
image |
file |
上传的文件名称 |
返回数据:
{
"success":true,
"code":200,
"msg":"success",
"data":"https://static.mszlu.com/aa.png"
}
导入七牛云依赖:
<dependency>
<groupId>com.qiniugroupId>
<artifactId>qiniu-java-sdkartifactId>
<version>[7.7.0, 7.7.99]version>
dependency>
11.2 Controller
@RestController
@RequestMapping("upload")
public class UploadController {
@Autowired
private QiniuUtils qiniuUtils;
@PostMapping
public Result upload(@RequestParam("image")MultipartFile file){
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,"上传失败");
}
}
11.3 使用七牛云
配置上传文件的大小:
# 上传文件总的最大值
spring.servlet.multipart.max-request-size=20MB
# 单个文件的最大值
spring.servlet.multipart.max-file-size=2MB
@Component
public class QiniuUtils {
public static final String url = "xxxxxxxxxxxx";
@Value("${qiniu.accessKey}")
private String accessKey;
@Value("${qiniu.accessSecretKey}")
private String accessSecretKey;
public boolean upload(MultipartFile file,String fileName){
Configuration cfg = new Configuration(Region.huanan());
UploadManager uploadManager = new UploadManager(cfg);
String bucket = "xppll";
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;
}
}
12.AOP实习统一缓存处理(优化)
内存的访问速度 远远大于 磁盘的访问速度 (1000倍起)
自定义注解:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cache {
long expire() default 1*60*1000;
String name() default "";
}
定义切面:
@Aspect
@Component
@Slf4j
public class CacheAspect {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Pointcut("@annotation(com.xpp.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)) {
params = DigestUtils.md5Hex(params);
}
Method method = pjp.getSignature().getDeclaringType().getMethod(methodName, parameterTypes);
Cache annotation = method.getAnnotation(Cache.class);
long expire = annotation.expire();
String name = annotation.name();
String redisKey = name + "::" + className+"::"+methodName+"::"+params;
String redisValue = redisTemplate.opsForValue().get(redisKey);
if (StringUtils.isNotEmpty(redisValue)){
log.info("走了缓存~~~,{},{}",className,methodName);
return JSON.parseObject(redisValue, Result.class);
}
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,"系统错误");
}
}
使用:
@PostMapping("hot")
@Cache(expire = 5 * 60 * 1000,name = "hot_article")
public Result hotArticle(){
int limit = 5;
return articleService.hotArticle(limit);
}
注意:像文章列表这样的接口用了缓存,刷新页面的时候浏览次数,评论次数不会变!!!
13.年月归档中MySQL查询
13.1 Controller
@PostMapping("listArchives")
public Result listArchives() {
return articleService.listArchives();
}
13.2 Service
@Override
public Result listArchives() {
List<Archives> archivesList = articleMapper.listArchives();
return Result.success(archivesList);
}
13.3 具体sql实现
查看数据库表发现是bigint
型
- 这里用一一个时间戳函数
FROM_UNIXTIME()
转化为日期类型,13位及以上bigint
要先除以1000
- 在用
YEAR()
和MONTH()
函数取出对应的年,月
<select id="listArchives" resultType="com.xpp.blog.dao.dos.Archives">
SELECT YEAR(FROM_UNIXTIME(create_date / 1000)) YEAR,
MONTH(FROM_UNIXTIME(create_date / 1000)) MONTH,
COUNT(*) COUNT
FROM ms_article
GROUP BY YEAR, MONTH;
select>
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);
articleMapper.update(articleUpdate,updateWrapper);
try {
Thread.sleep(5000);
System.out.println("更新完成了");
}catch (InterruptedException e){
e.printStackTrace();
}
14.对后端进行返回统一的标准格式
可以参考:SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的!
14.1 定义返回对象
@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);
}
}
14.2 定义状态码
可以将所有的状态码封装为一个枚举类,方便管理:
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;
}
}
15.项目亮点总结
-
jwt + redis
- token令牌的登录方式,访问认证速度快,session共享,安全性
- redis做了令牌和用户信息的对应管理,①进一步增加了安全性 ②登录用户做了缓存 ③灵活控制用户的过期(续期,踢掉线等)
-
threadLocal使用了保存用户信息,请求的线程之内,可以随时获取登录的用户,做了线程隔离。在使用完ThreadLocal之后,做了value的删除,防止了内存泄漏
-
线程安全- update table set value = newValue where id=1 and value=oldValue(CAS)
-
线程池应用非常广,面试7个核心参数(对当前的主业务流程无影响的操作,放入线程池执行)
-
权限系统(重点内容)
-
统一日志记录,统一缓存处理
最后喜欢的小伙伴,记得三连哦!
关注博主即可阅读全文
短视频app,基于 springboot + vue
该行业的大咖们每天都会在这里分享行业专业知识,用户可以在这里学习他们的从业经验的细节点,避免再犯这些错误,或者针对他们的分享,吸取有用的精华部分,进行专业知识 总结归纳,让他们的宝贵经验为你所用,当然...
Springboot + Vue +Mo ngoDB 项目 总结
qq_46212498的博客
02-03 249
Springboot是什么? &em sp;&em sp; Spring Boot为开发提供一个具有最小功能的 Spring应用程序, 开发 Spring Boot的主要动机是简化配置和部署 spring应用程序的过程。它使用全新的开发模型,避免了一些繁琐的开发步骤和样板代码和配置。就像 M aven 整合了所有的 Jar 包, Spring Boot 整合了所有的框架。 Spring Boot的主要特点
创建独立的Spring应用程序直接嵌入Tomcat,Jetty或Undertow(无需部署WAR文件)提供“初始”的POM文件内容,以简
napoluen的博客
06-24 861
Springboot + vue旅游 项目小 总结
此项目为一个springboot+vue入门级小项目,视频地址为:https://www.bilibili.com/video/BV1Nt4y127Jh
业务简单,对提升业务能力没什么大的帮助,更多的是可以熟悉开发流程和编码。
1.表结构
仅仅三张表,分别为用户表,省份表和景点表,其中省份表和景点表为一对多的关系。
用户表(t_user):
省份表(t_province):
景点表(t_place):
2.相关配置
server.port=8989
six_teen的博客
01-10 256
最近一段时间都在利用 springboot和 vue写一个自己的 博客系统,不只是个人系统,是一个类似csdn的 博客系统,目前已上线,访问地址如下: http://182.61.19.181:3000/ (ps: 由于经费有限,预计在2021-02-08后 服务器失效)。 在这次开发中(算的上是开发吧),遇到了挺多困难的,但也更让我熟悉 springboot和 vue等一系列框架就,以及自我解决问题的办法,同时也提升了代码的能力。 该 项目已托管于gitee,地址如下:https://gitee.com/six_teen
SpringBoot + vue 博客 项目所遇问题汇总
秋水小夕的博客
03-07 232
SpringBoot + vue前后端分离 博客 项目所遇问题汇总后端内容编辑所遇问题 vue前端内容编辑所遇问题
项目作者博客:https://blog.csdn.net/MarkerHub/article/details/106417097
后端内容编辑所遇问题
1、Spring Boot 类似Error creating bean with name “XXX”的错误
注意yml配置文件注意空格 和对齐形式
2、在idea中手动配置连接数据库
原因:服务器返回无效时区
解决方案1:
  在mysql命令执
安排, SpringBoot + Vue 博客系统 项目2020年
WantFlyDaCheng的博客
07-11 49
来源:来自网络,如侵权请告知博主删除????。仅学习使用,请勿用于其他~最近很多小伙伴和我要 博客系统相关的资源,安排上~目录01_1_ 项目简介与大纲.mp44 T4 g4 [% \- ...
java_zdc的博客
07-18 4080
前言:
技术准备
开发流程
项目展示
前言:
项目学习地址,点击了解:https://how2j.cn
天猫整站 Springboot 版本,就是这样一个实践项目。 我会带着大家,从零开始,把整个项目构建出来。成熟的项目规划与设计
本教程作者,也就是我~ 有8年企业开发管理经验,7年教学经验,曾参与管理开发几十个商业项目。将展示如何合理设计与规划这样一个规模的电商项目,既做到功能丰富,又让开发节奏有条不紊。基于Springboot 框架技术
本项目使用 Springboot 框架进行系统
SpringBoot + Vue 项目部署上线到 Linux 服务器
欢迎来到Gorit的博客
08-22 2060
SpringBoot + Vue 前后端分离 项目集成部署前言一、 Vue 打包的 项目如何部署?1.1 Vue 项目打包1.2 使用 Ex press 代理静态资源文件二、 SpringBoot 项目如何部署?2.1 数据库部署可能出现的问题2.2 SpringBoot 项目打包上传三、 服务器配置3.1 Nginx 基本配置3.2 Nginx 反向代理 SpringBoot 服务 总结
前言
有了一个基于 ElementUI 的电商后台管理系统,在开发一个相似的后台就会轻松很多。最近 Vue3 出来了,等这个后端管
springboot + vue前后端分离 项目(后台管理系统)
最新发布
先养只猫的博客
08-13 9833
学习笔记 学习资源来自于B站良心up 一、所使用的环境配置: 编译器:IDEA 后台框架: SpringBoot Mybatis-Plus 数据库:Mysql8.0 数据库工具:N avicat premium 前端框架: Vue Element UI 引用的富文本编辑器:wa ngEditor 二、 项目简介 这是一个基于 SpringBoot和 Vue的后台管理系统。 主要功能: 1.实现用户信息的CRUD,以及页面的显示。 2.用户权限的分配,不同权限的用户锁能看到的的界面信息和能进行的操作是不同的。 3.实现图片
音乐网站 SpringBoot + Vue 项目 总结
weixin_48669196的博客
07-14 60
音乐网站 SpringBoot + Vue 项目 总结 项目说明 项目配置业务分析实体类DAO层映射XML文件Controller层 项目说明 首页
技术栈:SpringBoot,Vue。后端功能相对简单,主要是增删查改,代码有些冗余,算是对之前SpringBoot知识的巩固。
项目配置
ot;1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://