面试:
自我介绍:
面试官您好,我是来自xxxx大学的xxx,目前正在学习Java后端相关的知识,包括MySQL数据库,redis数据库,使用过springboot完成过单体项目,了解springcloud及rabbitmq的使用。了解一些并发编程和jvm的内存模型知识。
项目面试:
1.介绍项目背景:
这个项目的起因是我当时刚好学习springboot顺便在网上找到一个练手项目学习,为了巩固自己的学习所以尝试了做一个仿照的项目。
项目的后端是我个人完成的,有一部分参考了网上单体项目的设计,然后通过postman还有swagger来调试接口的可用性。
2.介绍项目流程:
我是做了小程序和web网页端两个端的功能,没有再做其他端。
小程序端就是完成的对应的接口,主要是提供给用户使用的。完成了像用户登录,通过分类来查找不同的场地,确定分类后下单,还有订单模块。
web网页端主要是商户使用的,设计了场地管理,分类管理、位置管理、还有订单管理的功能。
3.小程序端的具体实现:
用户登录流程:
小程序的一般就是通过微信来登录,
就先是在controller里面写用户模块,在用户模块里面就完成那个用户登录的功能嘛,
先是解析前端传过来的DTO拿到微信的那个授权码,然后调用户服务里面的登录功能,获取微信用户的openid,还要做个判断看看用户是否合法这是之前学项目的时候看到的一种防止攻击的一种措施嘛。
然后拿到openid以后在数据库里面查找是不是有这个openid,相当于验证是不是新用户,如果是的话就要自动完成注册(自动注册就是封装user对象到数据库中,因为查是否注册也是在数据库中查),如果不是就不做别的操作了,直接返回这个用户信息就好了。
controller拿到服务返回的用户信息就封装成一个claims的map,然后用这个map拿到用户ID,
在调jwtUtil创建jwt令牌的方法将用户ID传过去,最后返回给controller,再把令牌还有其他的一些信息封装成用户的VO对象返回给前端。
jwt令牌:
就是先自己写了一个jwtutil类还有jwtproperties类来操作和生成jwt令牌作为token传递给前端,jwtproperties类就是操作我在yml配置里面配置好的哪些密钥啊、过期时间什么的。具体就是加了@component还有@configationproperties注解绑定yml配置来完成的。
jwtUtil类就是通过jwtproperties类里面配置好的属性,还有生成的claims作为参数,先指定签名算法,再生成jwt的创建时间,最后再用配置的密钥和算法、claims、还有时间封装成一个jwt对象返回。
其中jwtbuilder有个signwith方法,我在用的时候编译老是报错,后面看提示才说是他后面传的是字节数组,然后转换了一下才正常使用,但是很奇怪这里为什么要用字节数组,后面查了一下是好像是因为他这种密钥的格式一般就是字节序列存储的,然后可以跨语言跨平台,方便加解密,学的时候漏掉了这个知识点。
通过分类查找不同场地:
设想就是小程序里面发请求给后端要访问某个分类下的排球场地,比如访问气排球场地,显示所有场地,有空位就显示还有空位,没有就已满,然后再点击有空位的场地看看空出来的都是哪些位置,如果合适的位置就选择自己的位置,若是没有就可以退出看看其他场地。场地和位置我都在redis做了缓存,就是get和set操作,要是再redis没有找到就区MySQL里面查找,再写入缓存里。然后我在后台修改数据删除数据的时候就清空一遍缓存。
1). 导入Spring Cache和Redis相关maven坐标
2). 在启动类上加入@EnableCaching注解,开启缓存注解功能
3). 在用户端接口SetmealController的 list 方法上加入@Cacheable注解
4). 在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解
下单模块:
就是选定后下单,具体的那个支付模块还没有做,因为那个微信要注册商家还要写那个承诺书,所以这里的逻辑就是只要下单点击支付,前端给后端发起请求,后端就直接返回成功并将订单信息写进数据库。
但是有了解过微信支付那里的逻辑。微信支付分为好几种支付方式像是二维码支付、微信扫一扫、还有小程序支付等。比较熟悉的就是小程序支付,我就拿这个讲吧。他下单的时候后端就会从数据库取出金额是多少,然后通过时间戳的方式生成订单号,然后用LocalDateTime类生成当前时间作为下单时间。有了这些信息后,小程序端就会调用后端的微信支付接口,并且将前面的这些信息传递过来后端。我自己尝试过注册小程序但是是注册个人的,然后他会生成appid,然后用appid和openid确认操作的小程序和用户,openid是通过ThreadLocal里面做的一个获取目前登录用户id的方法获取到的。调用完微信支付接口后,他并不会直接完成支付,而是会生成一个预支付订单,用处是通知微信后台,可能一会会来一个订单,然后返回一个字符串,做一系列的数据处理和签名来保证安全。处理完后数据返回给前端作为参数,然后前端接收到这些数据后会调起微信支付,这些参数就来自于后端的预支付接口。点击前端出现的“确认支付”,请求微信后台,这时才是完成了真正的付款操作,付款成功就会返回支付结果,这里就已经完成了支付了。但后端还不知道微信完成了支付,所以微信后台还要根据notify_url来调用接口地址,通知后台,后台去修改数据库中的订单状态。
还要配置两个比较重要的文件:私钥文件、平台证书文件。
sky:
wechat:
appid: wxcd2e39f677fd30ba
secret: 84fbfdf5ea288f0c432d829599083637
mchid : 1561414331 #传智播客申请的商户号
mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606 #证书的序列号
privateKeyFilePath: D:\apiclient_key.pem #私钥文件(不是企业 没有)
apiV3Key: CZBK51236435wxpay435434323FFDuv3 #解密的秘钥(商户平台设置 一般公司提供好不用自己手动配置)
weChatPayCertFilePath: D:\wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pem #平台证书文件
notifyUrl: https://www.weixin.qq.com/wxpay/pay.php #支付成功的回调(域名地址+controller的访问路径)
refundNotifyUrl: https://www.weixin.qq.com/wxpay/pay.php #退款成功的回调
忘记了,下单模块还有一个上传照片的功能,使用阿里云的OSS
获取上传文件的原始文件名。
从原始文件名中提取文件的后缀名。
使用UUID生成一个新的文件名,将后缀名添加到新文件名中。
调用aliOssUtil的upload方法,将文件内容上传到OSS(对象存储服务)。
如果上传成功,返回一个包含上传文件路径的结果对象。
如果上传失败,捕获到IOException异常,打印出异常信息,并返回一个表示上传失败的结果对象。
注:
Logger用于记录日志信息。
AliOssUtil是阿里云OSS工具类,用于文件上传操作。
Result是一个结果对象,用于封装操作的结果信息。
@ApiOperation和@PostMapping是Spring Boot的注解,用于API文档和处理POST请求。
4.使用Redisson的RDelayedQueue来实现超时订单取消是一种高效且可靠的方法。RDelayedQueue是Redisson提供的一种延迟队列实现,它基于Redis的List和ZSet数据结构,可以用来处理需要在特定时间点执行的任务。
下面是一个使用RDelayedQueue实现超时订单取消的基本步骤和示例代码:
步骤
1.配置yml文件。
2.做一个configation类
3.创建RDelayedQueue实例。
4.将订单ID和超时时间放入队列。
5.当订单ID到达预定的超时时间时,从队列中移除并处理超时订单,即取消订单。
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
// 创建Redisson客户端
RedissonClient redisson = Redisson.create(config);
// 在OrderService里创建延迟队列对象。
RDelayedQueue<String> delayedQueue = redisson.getDelayedQueue("delayedQueue");
4. 实现超时订单处理
创建一个OrderTimeoutHandler类,用于监听延迟队列并处理超时订单:
// 添加订单到延迟队列,第一个参数是订单ID,第二个参数是超时时间(毫秒)
delayedQueue.offer("order123", 30 * 60 * 1000L); // 订单将在30分钟后超时
5.在你的主类或者一个独立的启动类中,调用startListening方法来开始监听延迟队列:
// 监听延迟队列,处理超时订单
delayedQueue.addListener((consumer, message) -> {
String orderId = message.getMessage();
System.out.println("Order " + orderId + " has timed out.");
// 在这里实现订单取消的逻辑
});
// 不要忘记关闭Redisson客户端
redisson.shutdown();
订单模块:
没有实现很复杂的功能,主要就是对订单表的一些crud还有接单和取消订单的接口。
5.商户端的具体实现都是一些基本根据业务情况的crud操作。
6.自定义注解基于AOP实现统一日志管理和公共字段填充,实现全局异常处理器,自定义枚举类型。
1.首先定义枚举类型,opration.type = insert/updata,只有删除和修改操作会更新操作人和操作时间。
2.为GlobalExceptionHandler添加@RestControllerAdvice,声明为全局异常处理器。然后通过exceptionHandler方法来拦截异常并打印异常信息。
3.自定义注解@Autofill,里面只有一个 OperationType value();
@Target(ElementType.METHOD) //只对方法有效
@Retention(RetentionPolicy.RUNTIME) //声明了此注解在程序运行期间也有效,可以通过反射动态处理这些注解信息。
4.基于AOP实现公共字段填充
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill) "),处理mapper里加了@Autofill注解的方法。
@Before("autoFillPointCut()")
public void AutoFil(JoinPoint joinPoint)
1.先加@before注解声明在方法执行前进行操作。
2.进入方法后,获取mapper里对应方法的MethodSignature方法签名对象,再用这个方法签名对象获取自定义注解对象
signature.getMethod().getAnnotation(AutoFill.class);再通过获取的自定义注解对象,获取注解里的值也就是操作的类型。
3.通过joinpoint获取实体对象。将该实体对象用Object类保存起来。
4.获取当前时间和操作者id。
5.进行判断操作类型,枚举类。
6.利用反射的getClass().getDeclaredMethod(对应方法的.class)来获取实体类的设置创建时间、创建人、更新时间、更新人
7.通过方法的对象名,调用invoke方法将对应的写进数据库中。
5.基于AOP实现统一日志管理。基本同上,新做一个切面类,自定义注解