后端
1. 项目中后端骨架搭建几个主要步骤是哪些?
在vueshop的开发文档中主要有以下步骤:
- 全局异常处理
- 统一结果封装
- 入参实体校验
- 跨域问题解决
2. 搭建项目开发架构需要考虑哪些问题
首先是技术栈选择,vueshop项目是基于springboot开发的,为了简化数据操作,数据层选用了mybatis plus,它自带的逻辑删除、分页、sql执行分析等插件对于微商城这项目也是作用很大。然后视频中,作者用了mybatiX插件可以快速生成代码,这大大提高了开发效率。然后就是业务分模块处理,这样层次分明,对于后期拆分成微服务很有帮助。但vueshop项目中有一点没做好,就是没有对安全问题进行处理,比如常见的xss、csrf等攻击。
3. 在代码层面,你觉得如何让项目更加安全?
添加常见的攻击预防,比如xss的,sql注入的。然后注册登录接口防止暴力破解,应添加ip黑名单等处理。实体接收前端传来的参数,最好采用赋值到新的对象中,防止其他字段被修改等。
4. 为什么需要统一结果封装,Result封装类里面得三个属性分别代表什么意思?
因为是前后端分离的项目,当前端访问后段的数据接口时候,使用统一结果数据封装,能让前端统一处理返回来的数据,通过code判断是否访问成功,msg一般指当操作失败或数据异常时候能够提示的消息,data是操作成功时候返回的数据集。因此,封装了Result类提高了前后端的沟通效率。
5. 为什么要自定义异常,什么情况需要抛出异常
商城业务中,比如生成订单,当某种必要条件不满足的时候应该抛出异常。而作为项目的业务上异常,通过自定义异常能够让项目不依赖其他包,更能统一处理。在vueshop中,一般来说在controller层如果条件不满足时候可以通过Result.fail返回数据失败的消息,如果是service类中,这通过抛出异常来处理数据上的异常,比如对象判空是通过Assert.notNull,实际上就是当对象为空时候就抛出异常。而抛出的异常会被全局异常处理器捕获,然后返回统一结果数据给前端。
6. 如何全局捕获异常,如何针对具体某种异常做对应处理
vueshop项目中,定义了GlobalExceptionHandler类来处理全局异常,类上添加类@RestControllerAdvice,作用是捕获来自所有的RestController中抛出的异常,然后方法上添加@ExceptionHandler注解指定具体处理哪种异常。
7. 项目报异常时候,如何快速定位报错代码
一定要看日志打印的控制台窗口,错误日志一定要上下看完整,最好能找到项目中的报错的类。有些人往往项目报错了,就马上去修改代码,直觉上是哪里哪里出错了,调试了半天,结果看控制台才知道不是哪里错。还有些人看到报出的异常,贴了一段springboot抛出异常信息去百度,结果搜索出一堆结果。
对于前端调试,多打印一下对象的信息,多使用console.log,方便调试。
8. 新增和编辑能否用同一个方法,什么情况应该分开?如何区分是新增还是编辑
vueshop中是同一个方法,区分新增还是编辑通过传过来的参数总有没对应实体的id来区分,有id说明是编辑。当新增或编辑的业务上区别很大时候,最好是分开写,避免方法内的代码量大不美观。
9. 前后端分离项目比传统项目有啥优点?
vueshop的前端是使用了vue3.2版本,我最直观的感受就是在js的编写上代码量大大减少了,操作dom和数据更加简单。而前端项目独立出来可以让后端开发更加专注。
10. 项目中dto的作用是啥?
vueshop中的dto是作为接收前端传过来的数据载体,然后通过validator框架给dto上的字段做参数校验。
11. 断言有啥用?原理是啥?如何合理使用assert断言
vueshop中大量使用了断言,通过断言来判断某中条件,比如判断对象是否为空,Assert.notNull,或者某个条件是否满足Assert.isTrue。Assert的底层就是当给的条件不满足时候就抛出异常,然后异常的信息是Assert上添加的错误提示。在业务上做需要判断时候可以使用Assert,这样条件不满足时候,防止程序继续向下走,并且能及时返回错误的信息。
12. 前后端分离项目,如何解决404问题?
项目中,在application.yml文件中添加了以下配置:
spring:
mvc:
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false
- 当没有找到处理请求的处理器时,抛出异常
- 禁用自动映射资源 并且全局异常处理中添加了@ExceptionHandler(value = NoHandlerFoundException.class)处理抛出的404异常。
13. 为什么 Spring和IDEA 都不推荐使用 @Autowired 注解
说实话,我还是觉得@Autowired注解使用上很方便,为了不让idea出现波浪线的提示,vueshop项目使用了@Resource的注解。但我看别人的博客,都是推荐使用构造方法注入的形式,可以配合lombok的注解使用。
14. 跨域问题是如何产生的?项目中如何解决?
跨域问题源于浏览器的同源策略限制。同源策略主要是限制cookie的访问,在非同源的情况下,A网页不能够访问B网页, “同源” 即是 协议 + 域名 + 端口。设计是为了防止CSRF(跨站请求伪造)。vueshop的跨域处理是在后台服务端处理的,服务端进行设置默认允许某些域名跨域访问。
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
...
registry.addMapping("/**")
.allowedOrigins("*")
可以看到代码中允许所有的来源访问,真正上线时候应该设置指定的前端项目域名最好。
15. 如何让接口更加优雅,为什么不在comtroller中写业务逻辑
controller中使用hibernate validator框架做参数的基本校验,满足条件才能进入业务代码中,业务处理都在service中处理,特别是涉及多次提交或更新操作需要使用事务时候,要在service总处理。因此controller的代码量不会很多。而比较新的springboot版本如果在controller层添加事务注解的话是会报异常的。因此如果要让接口更加优雅,service的方法名称要起的好,一眼就知道处理什么内容,然后又统一返回结果数据封装。
16. 写项目接口的过程中,你是如何测试接口的,是否能熟悉使用postman工具
其实很多前后端分离的项目都喜欢使用swagger框架来调试api的接口和自动生成接口文档。但vueshop中没有使用,而是使用了postman来调试接口,有好有坏吧。
17. 项目是个如何进行代码生成的,原理是啥
mybatis plus官网中提供了2中方式来生成代码,一种是使用代码形式,更加灵活、可以自定义生产模板等一系列操作。另外一种是idea安装插件myabtisX的形式,能界面化生成代码。在vueshop的视频讲解中,使用了mybatisX插件生成代码,的确很方便,代码生成其实就是使用freemarker模板引擎,动态填充数据生成对应文件。
18. 当你准备做一个项目,是如何去判断项目需要哪些表和字段的
vueshop的视频讲解中有说到,最好先确定项目需要开发什么功能,然后先自己粗略确定需要什么表和字段信息,然后看同样的开源项目,别人是怎么设计的,结合自己的需求保留字段或添加删除字段。
19. vueshop项目的分页是如何完成的?
使用了mybatis plus的分页插件,需要分页的方法中,只要添加Page参数,插件就会自动帮你完成分页的操作。
20. 为什么使用mybatis plus,带来了那些好处和坏处?
使用mybatis plus最大的好处就是解决了单表的增删改查问题,因为项目业务中,大部分的数据操作都是单表操作,因此大大提高了开发效率。还有其他插件的使用也对项目有好处。但也有坏处,就是做sql的调优时候不好,一些人滥用mybatis plus的方法,会照成后期维护困难。
21. 开发环境中,如何在控制台打印执行的sql语句?
依赖 p6spy 组件能够打印执行的sql,建议开发阶段都使用。
22. 前端提交数据到后台,后台如何检验元素是否符合规则
使用了hibernate validator框架,在实体字段上添加对应的条件注解,比如@NotNull等,然后controller中,接收参数时候添加@Valided注解,当条件不满足时候会抛出异常。
23. 删除多条记录时候,如何接收多个id
比如删除购物车中的商品接口:
public Result delete(@RequestBody Long[] ids) {
可以看到多个id是放在body中传过来的。 在前端,id必须放在数组中,就算是单个id也必须放在数组中,否则后段接收会报错,
const ids = ref([])
然后调用购物车删除商品接口时:
export function DeleteCart(ids) {
return request({
url: '/cart/delete',
method: 'post',
data: ids
})
}
配套微商城项目:点击这里学习
24. 在app端,是如何识别用户身份的
VueShop的前后端判断用户或管理员的身份方式是不一样的,管理系统设计到鉴权问题,所以用的是shiro框架,而app端则简单很多,定义一个@Login注解,在需要登录才能访问的接口添加上这个注解,然后设置一个拦截器AuthInterceptor拦截这个注解。拦截器中获取用户的jwt,判断是否合法,然后把userId存储到session中,这样在当前会话的操作中都能知道用户是谁了。
app端和管理系统都用到了jwt,密钥也是一样的,但是jwt两端是不通用的,比如app端登录获得的jwt是不能用于管理系统的,因为在生成jwt的时候设置了颁发机构,校验jwt时候会判断颁发机构是否一致。
25. app端,如何让用户登录后才能访问受限资源
只需要在对应的方法上添加上@Login注解即可,会有拦截器处理用户身份问题。
26. jwt的原理是啥,登录用户是如何完成退出的
jwt与token都能代表用户的身份,只不过jwt是把用户的id设置在jwt字符串上,通过密钥加密确保不会被修改,而token是把用户id存到存储中间件中,比如redis。因为jwt在后端不需要存储,因此用户退出时候后端其实是不需要做啥特殊处理的,只需要前端把存储在localStroge中关于该用户的任何信息清空即可。
27. token快过期时候,如何做到无感刷新token
VueShop项目中并没有做无感刷新token的处理。我认为,在检验到token快过期时候,我们只需要重新生成一个jwt放在返回的结果请求头中,可以起名为refreshToken,前端检验到请求头中有refreshToken中有信息,获取并覆盖原来的token信息即可,这样下次访问时候用的就是最新生成的token信息了。
28. 当用户的token过期,后端如何处理,前端如何判断的?
当token过期时候,后端身份校验的拦截器AuthInterceptor检验到过期,抛出异常提示“请先登录”,网络状态码设置为401,前端收到提示后弹窗提示登录,然后判断到网络状态码为401后,清空用户的所有信息,然后跳转到登录页面。
抛出的异常会被全局异常处理器捕获到,然后返回结果并设置网络状态码,如下:
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(value = UnauthenticatedException.class)
public Result handler(UnauthenticatedException e) {
log.error("未登录异常:----------------{}", e.getMessage());
return Result.fail(e.getMessage());
}
29. 在权限系统中,权限三要素是什么关系,如何控制管理员时候有某种权限
权限三要素主要是:管理员、角色、菜单权限。他们之间关系是:一个管理员可以有多个角色,一个角色有多个菜单权限,管理员与菜单权限之间一般不做直接关联,其他都是多对多关系,因此有2个中间表。
一般设置管理员的权限时候,一般先设置某个角色拥有的菜单权限,然后再给该管理员关联角色。受限资源方法上会添加上对应需要的菜单权限,这样当管理员访问该接口时候,需要先判断一下是否拥有该菜单权限,有才能通过,否则返回‘无操作权限’提示。
30. 菜单的上下级关系如何控制的
菜单之间是通过parentId字段来确定上下级关系的,最顶级的菜单parentId为0。如果要把菜单的上下级关系关联成树状结构时候,可以使用递归方法,或者Vueshop中的方法:
/**
* 把list转成树形结构的数据
*/
private List<SysMenu> buildTreeMenu(List<SysMenu> menus) {
List<SysMenu> finalMenus = new ArrayList<>();
for (SysMenu menu : menus) {
// 先寻找各自的孩子
for (SysMenu e : menus) {
if (e.getParentId() == menu.getId()) {
menu.getChildren().add(e);
}
}
// 提取出父节点
if (menu.getParentId() == 0L) {
finalMenus.add(menu);
}
}
return finalMenus;
}
31. 权限系统中,没有权限的按钮如何控制不显示的?具体用了什么?
// TODO
32. 对于商品详情是如何存储的,什么格式,前端使用到了什么插件展示内容
因为商品详情的内容都比较多,因此数据库中该字段设置的格式是text格式,管理系统前端在编写商品详情时候使用到的是开源的MD编辑器v-md-editor,会把得到的md格式的商品详情内容存储到数据库中,然后app端获取到内容时候也是使用v-md-editor插件的预览模式,把md转化成html渲染出来。
33. 商品的sku是个什么概念
商品sku是指:库存保有单位,即库存进出计量的单位。这是商品的销售属性集合,供买家在下单时点选,可以是规格、颜色、尺码等等。在vueshop微商城中,比如同样一个商品小米空调,但是选择的规格可以有多种,大一匹单冷空调与1.5匹单冷空调的库存价格都是不一样的。
34. 在项目中,系统如何解决sku存储问题
需要用到几个表:
- AppSpecification:商城通用规格属性
- AppSpecificationValue:商品实际规格属性值
- AppSkuStock:某SKU商品的库存价格信息表 首先在后台管理系统中,可以设置商品的规格信息:
可以看到上面的信息,可以设置商品规格名和值,多个规格值是以分号隔开存储到一个字段中的。而在价格/库存信息,也就是商品的sku信息,每一种组合都会有对应的价格和库存信息,然后保存到AppSkuStock中。获取商品详情时候,这2个表中对应的关联的记录都要查询出来。
35. 在前端,某种规格组合的库存为0,或者没有这种组合的商品时候,是如何控制不让用户选择的?
在上面的价格/库存设置中,是可以给某个组合的规格设置库存为0的,当某组合的库存为0时候,在app端就不可选这组合。然后前端如何知道哪些组合是可以选择哪些是不可选择,vueshop的处理方式是:给所有库存不为0的sku组合做一个幂集,这样当用户选择的规格在幂集中时候,说明是可选的,否则就是不可选的。详细内容可以参考app端Sku.vue的js代码,写得很详细。
36. 单规格和多规格的商品的数据是如何设计的?
商品新建完成之后默认规格是空的,也就是单规格 - 默认规格,如果该商品需要设置规格,这需要点击规格去设置对应的规格信息,这样设置之后就是多规格的商品了。当商品需要展示规格信息时候,先去AppSpecificationValue表中查看规格信息,如果查询为空说明是没有规格,也就是单规格的商品,这之后手动赋予对应的默认规格信息。
对应代码如下:
@Override
public List<AppSpecificationValue> listByProductId(Long productId) {
List<AppSpecificationValue> specificationValues = this.list(new QueryWrapper<AppSpecificationValue>().eq("product_id", productId));
if (specificationValues == null || specificationValues.size() == 0) {
specificationValues = getSingleSpec();
}
return specificationValues;
}
private List<AppSpecificationValue> getSingleSpec() {
AppSpecificationValue spec = new AppSpecificationValue();
spec.setSpec("规格");
spec.setValue("默认规格");
return ListUtil.toList(spec);
}
37. 项目中,图片是如何存储的,前端如何上传图片到后台接收
图片是上次到了腾讯云的cos中的,然后把得到的图片url存储到数据库中,多个图片url以分号隔开存储。
以上传商品评论图片为例:
前端发送有图片的表单时候是以form表单形式提交的,而不是json数据形式代码如下:
const submitHandle = () => {
let formData = new FormData();
for (const key in comment.value) {
formData.append(key, comment.value[key])
}
if (comment.value.pics.length > 0) {
for (let index = 0; index < comment.value.pics.length; index++) {
const pic = comment.value.pics[index]
//element.file取的是File文件对象
formData.append("pics", pic.file);
}
}
PostComment(formData).then(res => {
Notify({type: 'success', message: '评论成功'})
router.replace('/comment/info?id=' + res.data)
})
}
可以看到需要把json格式转成form表单信息然后提交数据。其中PostComment请求接口时候需要一点特殊设置:
export function PostComment(data) {
return request({
url: '/comment/post',
method: 'post',
processData:false,
contentType:false,
data
})
}
可以看到processData:false, contentType:false的设置,是为了防止把图片转化成字符串格式。这样后端就能接收到图片信息:
@Login
@PostMapping("/post")
public Result post(MultipartFile[] pics, @Validated AppComment comment) {
...
}
38. 项目中,如何压缩图片的?
压缩图片使用到了hutool的Img工具类:
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Img.from(file.getInputStream())
.scale(scale)// 缩小
.setQuality(quality)//压缩比率
.write(outputStream);
压缩后的图片在outputStream输出流中。
39. 在购物车中,当购买的商品数量发生变化,或者被编辑删除时候,总价是如何重新计算的
Vueshop的后端api中有一个统计商品价格的接口,当购物车中的商品信息发生变化或者勾选状态发生变化时候会重新调用该接口重新计算价格。
40. 通过购物车和立即购买产生订单的逻辑是否是一样的,2个流程有什么区别
用户下单有2中方式:
- 购物车购买
- 立即下单 其中立即下单是不需要经过购物车的,因此商品的id、购买数量和选择规格信息不可缺少,需要把这些信息组合成一个临时的购物车对象。
立即购买只能购买一种规格的商品。而购物车方式可以把多个规格多个商品的信息添加到购物车中然后一起提交。
41. 用户下单的过程,需要做哪些检验?
下单时候收货地址不能为空,商品或购物车信息不能为空,更新商品库存信息时候需要同时校验库存是否足够,完了之后 添加订单到超期任务队列,30分钟内未支付的订单重新释放库存,并自动设置为取消订单状态。
配套微商城项目:点击这里学习
42. 下单过程,如何做到控制重复下单
在VueShop的app前端,当用户点击立即下单时候,按钮就立即转成加载不可点击状态,防止重复提交。同时在后端,定义了@NoRepeatSubmit注解,和切面拦截器RepeatSubmitAspect,在切面拦截器中,使用Redisson对拦截的方法进行加锁,锁定时间默认为5秒,获取锁成功则可以进行下单操作,否则提示别重复下单。锁会在5秒后会自动释放锁,这时候又可以下单操作了。
43. 能否简单说下商城从加入购物车到确认收货,整个订单的状态的变化
vueshop中的订单状态设置相对比较简单,比如取消状态,没有区分是用户取消还是超时取消等。当然了,订单状态复杂了,相对来说编码上就会负责很多。估计是考虑到很多新手学员吧。
商品加入购物车后并没有生成订单,需要提交订单经过订单预览填写收货地址后写单,这时候订单状态是【待支付】状态,超30分钟如果未支付系统会自动取消订单,用户也可以自主取消,即【已取消】状态。用户支付完成之后是【待发货】状态,后台管理员选择物流公司,填写物流信息进行发货,状态进入【待收货】状态,用户可以查看物流信息,收货后可以确认收货,即进入【已完成】状态,已完成的订单里面的所有商品可以进行评论,因此待评论中,可以选择对应商品进行评论,评论并不会影响订单的状态。
其中【待发货】【待收货】【已完成】的订单都是可以申请退货退款操作的,申请后会产生一条申请退货退款记录AppRefund,退款表记录为【待处理】状态。
【待发货】的订单申请退款后,因为未发货,所以退款不需要退货,退款实时完成的,因此申请退款后订单进入【退款成功】状态,退款进入【已完成】,而【待收货】【已完成】状态的订单因为已发货了,所以申请退款后订单进入【退款中】的状态,退款记录为【待处理】,后台需要对申请退款的订单进行审核处理,对于人为或其他原因照成商品损坏的可以拒绝退款操作,订单进入【退款失败】状态,退款记录进入【已拒绝】,订单完成。如果审核通过,则订单的【退款中】不变,退款记录则进入【退货中】状态,当管理员收到返回的商品时候,确认退款退货,订单进入【退款成功】,退款记录进入【已完成】状态。
44. 项目中,是如何做到未支付订单30分钟后自动取消的
使用到了RabbitMq的死信队列,当未支付订单的id进入到mq的正常队列中,超过30分钟未被消费时候,id消息会被抛到死信队列中,被死信队列的消费者获取到,然后判断该订单是否已经支付,如果未支付就设置订单状态为已取消。已支付完成的订单就不做任何处理即可。关于死信队列是rabbitmq自带的功能,如何配置可以参考app端的具体代码。
45. 为什么需要订单快照?
用户下单成功之后,购买的所有的信息都应该记录下来,作为一个快照信息。因为购买的商品信息后台是可以随时调整的,比如价格信息等。如果没有快照,那么当商品或收货地址等信息发生变化后,就无法确认并还原当时购买商品的信息,会照成信息丢失和错乱,因此必须要存储快照,快照信息其实可以单独成一个表。VueShop项目中是存储到AppOrder表中的。
46. 在项目中,商品,订单的信息为什么不能测底删除
因为在整个微商城中,商品表是和很多其他表都是有关联的,比如购物车表,当商品被删除后,造成id记录的丢失,那么购物车关联时候就可能出现空指针错误。当然了,这也很好处理。其实最主要就是这些商品有可能是被购买过的商品,保留商品的记录可以方便后期做一些归档整合等处理。订单不能彻底删除是因为订单是需要统计营业额等信息的。
因此需要做到保留记录又有删除的效果,可以使用逻辑删除,即通过一个字段的变化表示删除效果。
47. 项目中,逻辑删除是什么原理?底层逻辑是啥?
vueshop项目用了mybatis plus的逻辑删除的插件,全局指定逻辑删除的字段是isDelete,0表示未删除,1表示已删除。调用mybaits plus的list或get等接口时候会自动添加上is_delete = 0的条件判断。当然了自己手写sql的时候是需要手动添加上去的。
48. 如何对接支付宝,支付宝回调是如何保证接口安全的?
关于对接支付宝支付接口,其实使用一些开源的封装工具会更加简单,在vueshop的视频讲解中,作者是通过查看支付宝的开发文档完成对接的,当然了这也很简单,有沙箱环境可以进行测试。关于保证数据安全,不管是传参还是支付宝回调,都是有公钥密钥等加密工具保证数据不会被修改,从而保证接口数据安全的。
49. 如何避免重复支付?
同样是使用了避免重复下单中的注解@NoRepeatSubmit。
50. 扣减库存,是如何保证不超卖的?
在vueshop中,是从sql层面来控制超卖问题的,比如单规格产品的扣减库存的sql如下:
update app_product set stock = stock - #{quantity}
where id = #{productId} and stock >= #{quantity}
多规格产品扣减库存如下:
update app_sku_stock set stock = stock - #{quantity}
where id = #{skuId} and stock >= #{quantity}
可以看到,只有库存大于购买数量时候sql才会被执行,所有保证不超卖问题,但是这样对数据库压力挺大的。 也还可以通过其他方式,比如通过添加分布式锁,在计算和扣减库存的方法中添加锁,这样也能保证不超卖。或者乐观锁方式也是可以的。
51. 什么时候扣库存,什么时候恢复库存?
单规格商品的库存信息是存在AppProduct表中的,而多规格商品的库存信息则是在AppSkuStock中。而这2个表都是直接通过stock字段来存储库存的,并没有预扣库存的概念,所以扣减或恢复库存都是直接对stock字段进行操作。
当用户下单之后,系统就已经开始扣减对应的商品库存了,但是如果超时未支付或者主动取消订单,是会恢复对应的库存数量的。
52. 商品搜索是如何设计的?
搜索模块借助了搜索中间价elasticsearch来提高搜索的效率,因此发起搜索动作时候其实是不需要对数据库的表进行查询的,而是直接查询es中的索引信息。而es中的数据会在项目启动时候就会自动同步一遍,我觉得其实可以在管理系统中做一个按钮,点击全量同步mysql与es的数据。然后当管理系统添加修改或删除商品的信息时候,会把商品的id作为消息发布到对应mq中,然后消费端获取到商品的id后,通过查询数据库获取到最新的数据,然后同步到es中,这样保证es与mysql的数据同步。
53. es中商品的信息是如何和数据库中保持一致的
vueshop中,采用了rabbitmq作为增量数据同步的中间件,商品数据发送变化时候先发到mq,然后再从mq中获取消息同步数据到es中,这样做的好处处理起来比较简单,但是多了一个需要维护的rabbitmq中间价以及队列等。
之前作者做过另外一个项目,叫dailyhub,在es与mysql之间的数据同步,用到了canal中间价,这样在编码层面就不需要做同步,直接在服务器中配置好canal即可,但是canal这东西安装和维护起来需要有点基础。
54. docker如何部署es,如何操作线上的es数据
docker安装es其实很方便,几条命令即可:
docker pull elasticsearch:7.16.2
docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" --name="es7162" -d elasticsearch:7.16.2
然后涉及到中文,所以需要安装中文分词器:
docker exec -ites es7162 /bin/bash
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.16.2/elasticsearch-analysis-ik-7.16.2.zip
当然了,详细的配置可以查看作者写的vueshop的完整开发文档 查看或操作线上数据可以安装可视化工具来查看。
55. 项目中,mq起到了什么作用
主要用在了2个方面:
- 用于订单超时未支付自动取消,用到了死信队列
- 用于同步mysql与es之间的商品数据
56. 前后端分离项目如何部署到云端
一般都会分开部署,后端使用docker部署,前端经过build之后,在nginx中配置对应的路径信息即可。
57. 项目发布新版本时候,如何快速迭代完成部署和上线
可以通过编写shell脚本,借助jenkins等工具完成迭代式上线部署。
58. 项目如何拆分为多个服务项目,拆分标准怎么判定
没什么大标准,需要根据实际的业务流量来决定需不需要拆分,如果流量不多,甚至不用拆分,因为服务多了之后维护起来特别麻烦,服务越多越麻烦,给调试和开发部署都有不同的压力。
59. 从项目学习中,你学会了那些编码技巧?
service中条件判断是以使用Assert断言处理;mybatis plus非常强大,应该学会灵活运用,大大减少编码数量;开发比较复杂的业务,可以先写注释,确定步骤之后在完成编码。死信队列非常好用,配置也简单;
60. 如何估计使用缓存,提高接口性能
微商城是读多写少的项目,流量也主要集中在首页、商品分类和商品详情这几个页面中,因此,可以着重对这几个页面的接口添加缓存,这样可以提高访问的速度。可以整合cache模块,使用注解方式来缓存数据。
61. 请编写sql,查出今日营业额,本周营业额,本月营业额
根据给出的实体类字段,可以编写如下 SQL:
-- 今日营业额
SELECT COALESCE(SUM(total_amount), 0) AS today_sales
FROM app_order
WHERE pay_time >= DATE(NOW())
AND order_status IN (1, 2, 3);
-- 本周营业额
SELECT COALESCE(SUM(total_amount), 0) AS weekSales
FROM app_order
WHERE YEARWEEK(pay_time, 1) = YEARWEEK(NOW(), 1)
AND order_status IN (1, 2, 3);
-- 本月营业额
SELECT COALESCE(SUM(total_amount), 0) AS tmonthSales
FROM app_order
WHERE YEAR(pay_time) = YEAR(NOW())
AND MONTH(pay_time) = MONTH(NOW())
AND order_status IN (1, 2, 3);
这里的 COALESCE() 函数用于处理当结果集为空的情况,将其转换为零。 YEARWEEK() 函数可以获得某个日期属于当年的第几周,第二个参数 1 表示以星期一作为一周的第一天(如果改为 0,则以星期日作为第一天)。
62. 请编写sql,近三天,本周或本月每日的营业额
SELECT DATE(created) as date, SUM(total_amount) as total
FROM app_order
WHERE created >= CURDATE() - INTERVAL 3 DAY
GROUP BY DATE(created)
ORDER BY date;
63. 编写sql,查出近7天每日的用户注册量
SELECT DATE(created) AS day, COUNT(*) AS register_count
FROM app_user
WHERE created >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY day;
这个查询会将最近7天内的用户按照注册日期分组,然后统计每组内有多少用户,最后输出每日的用户数。需要注意的是,这里使用了 MySQL 的 DATE 函数来提取出日期部分,并使用 DATE_SUB 函数计算出7天前的日期。如果使用其他数据库,可能需要使用不同的函数来完成相同的操作。
前端
64. 前端把用户的身份信息存储到哪了?为什么刷新项目,信息没有丢失?
vueshop的前端是一个基于vue3.2开发的前后端分离项目,有集成了vuex的插件,可以集中式管理数据状态,用户登录完成之后的身份凭证jwt是可以存在store中的。但是因为浏览器刷新之后会丢失数据,所以jwt是存储在localStorage中,这样就是存在浏览器中,所以即使刷新了项目,但是因为浏览器localstorage中的用户身份信息没有清除,所以是可以重新获取到的。
65. 在前端,是如何判断分页的数据是否是最后一页的?
比如说订单列表的代码:
if (data.orders.length === res.data.total) {
data.finished = true
}
因为用到了瀑布流的效果,所以加载下一页数据的时候上一页的数据是不会替换掉的,所以判断是否最后一页可以通过判断加载出来的订单列表的长度是否等于订单总数即可。
66. 前端中,axios发起请求前后,做了什么?
前端需要请求后端接口,所以vue项目中,一般借助axios来发送网络请求。在用户登录完成之后,用户会获取到一个身份凭证的token,发送请求时候需要带上token的信息,而一般每一个请求都需要这样处理,所以为了简化开发工作,可以在axios发起请求前添加一个拦截器,添加上token的信息,这样每次请求都会自动带上token信息。
请求完成之后会获取到接口返回来的数据,这时候可以做个统一处理,所以给axios对象添加一个后置拦截器,通过判断返回来的数据的code和网络状态码是否正常,如果不正常可以弹窗提示消息或者其他处理。
另外因为可以请求前可以添加一个加载动画loading。请求完成或者出现异常时候结束加载动画即可。
67. app前端项目中,是怎么做到无刷新返回上一步的?
68. vue3.2的父子组件是如何传递数据的?
父传子:
<!-- 父组件中通过给子组件自定义属性传递值 -->
<Subassembly :value="doc"/>
子组件获取参数:
import { defineProps } from 'vue'
// 接收父组件传递过来的值
const props = defineProps(['value'])
子传父:子组件是不能直接给父组件传值的,可以通过调用父组件方法方式传参。
父组件中:
<Subassembly :value="doc" @func="sayHello" />
// 待传递的方法
const sayHello = function (data) {
alert(data);
}
子组件:
const emit = defineEmits(['func'])
const handelClick = function () {
//调用父组件传递过来的方法,传入参数修改父组件的值
emit('func', 'hello world')
}
69. 父子组件之间的方法如何相互调用
父调子:
// 子组件
function subCompFunc() {
console.log("subCompFunc is called!");
}
defineExpose({ subCompFunc });
父组件:
<childcomp ref="childComp"/>
const childComp = ref();
function selfFunc() {
childComp.value.subCompFunc();
}
子调父见上(68),略。
70. 权限系统中,前端如何做到动态路由控制的?
具体如下,详情可以查看后台管理系统的前端开发文档。
// 动态绑定路由
let newRoutes = router.options.routes
const addRouter = (menus) => {
menus.forEach(menu => {
let route = menuToRoute(menu)
if (route) {
router.addRoute('layout', route)
newRoutes[0].children.push(route)
}
if (menu.children) {
// 递归
addRouter(menu.children)
}
})
}
// 本语句导入指定目录下所有符合条件的文件
const modules = import.meta.glob('./views/*/*/*.vue')
// 导航转成路由
const menuToRoute = (menu) => {
if (!menu.component) {
return null
}
let route = {
path: menu.path,
name: menu.name,
meta: {
icon: menu.icon,
title: menu.title
}
}
route.component = modules['./views/' + menu.component]
return route
}
menus是之前通过获取用户信息获取到的管理员等看到的菜单列表,然后通过循环该菜单列表把菜单转成路由,并添加到项目的路由管理中心router中,并且也需要管家到layout的children中。就是上面这两句代码:
router.addRoute('layout', route)
newRoutes[0].children.push(route)
router就是import router from '@/router'获取的,获取到的就是项目的路由中心,router.addRoute('layout', route)就是把let route = menuToRoute(menu)通过菜单组成的路由添加父路由layout中,这样就完成父子路由的设置。 newRoutes[0]表示获取第一路由组件,上面我们写route/index.js的时候layout这组件就是写在第一个的,所以这里就是获取layout这个组件。然后push到children中。
通过这设置,就可以动态添加路由了。
71. vue项目如何打包和部署?
项目执行npm run build,得到一个dist文件夹,然后上传到服务器一个合适位置,然后配置一下nginx的引用和访问路径即可访问。
配套微商城项目:点击这里学习