Springboot+Vue博客项目总结

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

//扫包,将此包下的接口生成代理实现类,并且注册到spring容器中

@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) {

//8080前端使用的端口号

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层还是servicedao层,都有可能报异常,如果是预料中的异常,可以直接捕获处理,如果是意料之外的异常,需要统一进行处理,进行记录,并给用户提示相对比较友好的信息。

  • @ControllerAdvice:对加了@Controller的方法进行拦截处理,AOP的实现

  • @ExceptionHandler:统一处理某一类异常,从而减少代码重复率和复杂度,比如要获取自定义异常可以@ExceptionHandler(BusinessException.class)

//作用:对加了@Controller的方法进行拦截处理,AOP的实现

@ControllerAdvice

public class AllExceptionHandler {

//进行一次处理,处理Exception.class的异常

@ExceptionHandler(Exception.class)

//返回json数据,不加的话直接返回页面

@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部分是否合法。

依赖包:

io.jsonwebtoken

jjwt

0.9.1

工具类:

public class JWTUtils {

//密钥

private static final String jwtToken = “123456Mszlu!@#$$”;

//生成token

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;

}

//检查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加密的依赖包:

commons-codec

commons-codec

@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) {

//1.检查参数是否合法

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());

}

//用md5加密

password = DigestUtils.md5Hex(password + slat);

//2.根据用户名何密码去user表中查询 是否存在

SysUser sysUser = sysUserService.findUser(account, password);

//3.如果不存在 登录失败

if (sysUser == null) {

return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(), ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());

}

//4.如果存在 使用jwt 生成token 返回给前端

String token = JWTUtils.createToken(sysUser.getId());

//5.toekn放入redis,设置过期时间。登录认证的时候先认证token字符串是否合法,在认证redsi认证是否合法

redisTemplate.opsForValue().set(“TOKEN_” + token, JSON.toJSONString(sysUser), 1, TimeUnit.DAYS);

return Result.success(token);

}

}

/**

  • 根据account和password查询用户表

  • @param account

  • @param password

  • @return

*/

@Override

public SysUser findUser(String account, String password) {

LambdaQueryWrapper queryWrapper=new LambdaQueryWrapper<>();

queryWrapper.eq(SysUser::getAccount,account);

queryWrapper.eq(SysUser::getPassword,password);

//需要id,account,头像avatar,naickname昵称

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.获取用户信息


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SK3ukVrG-1638022304550)(../../../AppData/Roaming/Typora/typora-user-images/image-20211125224045403.png)]

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

/**

  • 根据token查询用户信息

  • @param token

  • @return

*/

@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.setAccount(sysUser.getAccount());

loginUserVo.setAvatar(sysUser.getAvatar());

return Result.success(loginUserVo);

}

/**

  • 校验token是否合法

  • @param token

  • @return

*/

@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;

/**

  • 在执行controlle方法之前执行

  • @param request

  • @param response

  • @param handler

  • @return

  • @throws Exception

*/

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

/**

  • 1、需要判断请求的接口和路径是否为 HandlerMethod(controller方法)

  • 2、如果token是否为空,如果为空,为登录

  • 3、如果token不为空,登录验证 loginService->checkToken

  • 4、如果认证成功,放行

*/

if (!(handler instanceof HandlerMethod)) {

//handler可能是RequestResourceHandler 放行

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;

}

//登录验证成功

//用ThreadLocal保存用户信息

UserThreadLocal.put(sysUser);

return true;

}

@Override

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

//如果不删除,ThreaLocal中用完的信息会有内存泄漏的风险

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) {

//拦截test接口,后续实际遇到需要拦截的接口时,在配置为真正的拦截接口

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 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注解,当然前提是,这个方法所在的类必须在Spring环境中。

@Async(“taskExecutor”)

//期望此操作在线程池执行。不会影响原有的主线程

public void updateArticleViewCount(ArticleMapper articleMapper, Article article) {

Article articleUpdate = new Article();

int viewCounts = article.getViewCounts();

articleUpdate.setViewCounts(viewCounts + 1);

LambdaQueryWrapper

queryWrapper = new LambdaQueryWrapper<>();

queryWrapper.eq(Article::getId, article.getId());

//为了在多线程的环境下,线程安全 CAS思想,防止此时修改的时候已经被修改了(乐观锁)

queryWrapper.eq(Article::getViewCounts, viewCounts);

//第一个参数用于生成set条件,第二个生成where语句

//update article set view_count =100 where view_count==99 and id =xxx

articleMapper.update(articleUpdate, queryWrapper);

try {

Thread.sleep(5000);

System.out.println(“更新完成了!”);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

这里的update用法:

// 根据 whereWrapper 条件,更新记录

int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper 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) {

//拦截test接口,后续实际遇到需要拦截的接口时,在配置为真正的拦截接口

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(parentnull||parent0){

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);

}

//防止前端 精度损失 把id转为string

//分布式id 比较长,传到前端 会有精度损失,必须转为string类型 进行传输,就不会有问题了

@JsonSerialize(using = ToStringSerializer.class)

private Long id;

10.AOP统一记录日志


关于AOP的文章可以参考:

自己实现一个日志注解

//Type代表可以放在类上面,METHOD代表可以放在方法上

@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);

//获取request 设置IP地址

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”

}

导入七牛云依赖:

com.qiniu

qiniu-java-sdk

[7.7.0, 7.7.99]

11.2 Controller

@RestController

@RequestMapping(“upload”)

public class UploadController {

@Autowired

private QiniuUtils qiniuUtils;

@PostMapping
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

前端面试题汇总


前端面试题是我面试过程中遇到的面试题,每一次面试后我都会复盘总结。我做了一个整理,并且在技术博客找到了专业的解答,大家可以参考下:

由于篇幅有限,只能分享部分面试题,完整版面试题及答案可以【点击我】阅读下载哦~

感悟


11.1 接口说明

接口url:/upload

请求方式:POST

请求参数:

| 参数名称 | 参数类型 | 说明 |

| — | — | — |

| image | file | 上传的文件名称 |

返回数据:

{

“success”:true,

“code”:200,

“msg”:“success”,

“data”:“https://static.mszlu.com/aa.png”

}

导入七牛云依赖:

com.qiniu

qiniu-java-sdk

[7.7.0, 7.7.99]

11.2 Controller

@RestController

@RequestMapping(“upload”)

public class UploadController {

@Autowired

private QiniuUtils qiniuUtils;

@PostMapping
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-uRkF4W5V-1712477446185)]

[外链图片转存中…(img-4GXrtjmY-1712477446185)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-f5jmSQ4n-1712477446186)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

前端面试题汇总


前端面试题是我面试过程中遇到的面试题,每一次面试后我都会复盘总结。我做了一个整理,并且在技术博客找到了专业的解答,大家可以参考下:

由于篇幅有限,只能分享部分面试题,完整版面试题及答案可以【点击我】阅读下载哦~

感悟

春招面试的后期,运气和实力都很重要,自己也是运气比较好,为了回馈粉丝朋友们(毕竟自己也玩了这么久哈哈哈),整理个人感悟和总结以上。最后祝愿大家能够收获理想offer!!

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值