苍穹外卖面试篇(技术选型)

一.nginx

1.nginx反向代理好处:

1. 提高访问速度(可以进行缓存,如果访问相同资源可以直接响应数据)

2. 可以进行负载均衡(如果没有nginx前端只能固定地访问后端某一台服务器,加入nginx则可以将请求分发给后端不同的服务器)

负载均衡:把大量的请求按照、我们指定的指定的方式均衡的分配给集群中的每台服务器。

3. 保证后端服务安全(前端不能直接请求到后端服务器,需要通过Nginx转发)

2.nginx反向代理的搭建:

location /api/ 的意思是如果请求能匹配上/api/这个字符串。

proxy_pass 该指令的作用是设定转发的目的地,其后跟的是转发的目的地址。

3.nginx负载均衡的配置:

在webservers里面定义一组服务器,用于承接访问负载:

 4.nginx负载均衡的策略:

二.gitee

如果已有本地仓库,若想移除重新添加,下面是移除本地仓库的方法,首先在settings中移除本地仓库,然后关闭idea,把仓库地址下的.git、.idea、.gitignore文件删除,重新启动idea打开项目即可

如何团队协作管理git,这个项目里面讲的不是很多,可以在网上自行查询

三.JWT令牌

1.JWT含义

JWT全称为JSON Web Token,是一种用于身份验证的开放标准。它是一个基于JSON格式安全令牌,主要用于在网络上传输声明或者用户身份信息。JWT通常被用作API的认证方式,以及跨域身份验证。

2.JWT组成

3.JWT使用流程

1.用户向服务器发送登录请求,服务器进行身份验证,如果验证成功则返回一个JWT令牌给客户端。

2.客户端收到JWT令牌后,将其保存在本地。每次向服务器发送请求时,在请求的头部中携带该令牌,以便服务器对请求进行身份验证。

3.服务器收到请求后,从请求头中提取JWT令牌,并进行解析和验证。如果令牌有效,则允许请求继续执行;否则返回错误信息。

4.本项目中的JWT详细运用

(一个是员工登录验证,一个是用户登录验证)

1.首先引入jwt的依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
</dependency>

2.创建JwtUtil工具类

其中jwt签名加密使用的秘钥secretKey,jwt过期时间,前端传递过来的令牌名称都是在配置文件application.yml中配置好的

还需注意的一点是,看代码就知道,加密和解密使用的秘钥都是相同的,所以jwt是对称加密

public class JwtUtil {
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,
                // 一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);

        return builder.compact();
    }

    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }

}

3.创建jwt令牌校验拦截器

其中员工jwt拦截器和用户jwt拦截器是相互独立的

/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        log.info("开启客户端拦截器");
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:", empId);
            BaseContext.setCurrentId(empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

代码中 HandlerMethod 解释:
HandlerMethod 是 Spring MVC 框架中的一个类,用于表示处理 HTTP 请求的控制器方法。具体来说,HandlerMethod 包含了以下信息:
.Controller 类:处理请求的控制器类。
.方法:控制器类中处理请求的具体方法。
.方法参数:方法的参数列表。
.方法返回值:方法的返回值类型。

用途:                                                                                                                                             在 Spring MVC 中,当一个 HTTP 请求到达时,DispatcherServlet 会根据请求的 URL 和其他信息找到对应的控制器方法(即 HandlerMethod)。HandlerMethod 对象用于存储和操作这个方法的相关信息。

4.注册jwt拦截器

这里需要注意的是,在 Spring MVC 中,拦截器需要通过配置类(如 WebMvcConfiguration)进行注册,才能在请求处理过程中生效


/**
 * 配置类,注册web层相关组件
 */
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    @Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
    @Autowired
    private JwtTokenUserInterceptor jwtTokenUserInterceptor;

    /**
     * 注册自定义拦截器
     *
     * @param registry
     */
        protected void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册自定义拦截器...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**")//拦截器应该应用的路径
                .excludePathPatterns("/admin/employee/login");//排除的路径

        registry.addInterceptor(jwtTokenUserInterceptor)
                .addPathPatterns("/user/**")
                .excludePathPatterns("/user/user/login")
                .excludePathPatterns("/user/shop/status");
    }

  }

四.Swagger

有些接口需要的参数很多,若使用postman进行测试,就需要构造非常多的参数,测试的效率就会较低。此时,我们就可以通过swagger来帮助我们后端生成接口文档,并且可以进行后端的接口测试。

1.介绍

使用Swagger只需要按照规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。Swagger可以帮助后端生成接口文档、进行在线接口测试。

Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。

2.Swagger使用步骤

1.导入knife4j依赖

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-starter</artifactId>
    <version>3.0.2</version>
</dependency>

2.在配置类WebMvcConfiguration中加入knife4j 相关配置       

它会扫描controller里面的所有方法,然后通过反射去解析,最终生成接口文档。

3.设置静态资源映射,否则接口文档页面无法访问 

设置静态资源映射时,其方法名必须为addResourceHandlers,因为当前这个类继承自WebMvcConfigurationSupport(spring提供的一个类),addResourceHandlers其实是重写的父类WebMvcConfigurationSupport中的方法

  {
    /**
     * 通过knife4j生成接口文档
     * @return
     */
    @Bean
    public Docket docket1() {
        log.info("开启苍穹外卖项目管理端接口文档");
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .groupName("管理端接口")
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }
    
    /**
     * 设置静态资源映射
     * @param registry
     */
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

}

4.Swagger常用注解

 五.PageHelper

这里需要看项目中的代码,PageHelper和EmployeePageQueryDTO实体类的定义,以及EmployeeService中的使用,mapper层的使用

1.PageHelper的startPage方法可以通过传入的参数自动设置Limit,传入的是页码和每页的记录数,好处是:字符串的拼接不用自己做。底层实现是:它会给ThreadLocal设置上述参数,然后在执行SQL语句时会自动被取出,然后拼接成Limit。

2.Page是PageHelper插件定义的一个泛型类,是一个固定的返回类型。

六.格式化时间

1.方法一:

在实体类中的LocalDateTime属性上加上@JsonFormat注解,格式化时间。

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;

2.方法二:

拓展Spring MVC的消息转换器,统一对后端返回给前端的数据进行转换处理:

也是在配置类WebMvcConfiguration中创建

    /**
     * 扩展Spring MVC框架消息转换器  统一对后端返回给前端的数据进行转换处理
     * @param converters
     */
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("扩展消息转换器........");
        //创建一个消息转换器对象
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        //需要为消息转换器设置一个对象转换器,对象转换器可以将java对象序列化为json数据
        converter.setObjectMapper(new JacksonObjectMapper());//在JacksonObjectMapper里面有关于日期时间的序列化和反序列化器。
        //将自己的转换器加入到容器中
        converters.add(0,converter); //容器自带消息转换器,默认新加的排在末尾,0表示是首位,自己加的消息转换器排在首位
    }

七.AOP

1.业务分析

在多个业务表中都有公共字段,如create_time、create_user(insert时用到);update_time,update_user(insert和update时用到)这些。

插入数据的时候需要为这些字段赋值,会有大量重复的冗余set方法代码,后期如果表结构发生变化,代码需要跟着修改,此时就不方便修改(如果后期进行修改要重复一个个进行修改)。

2.实现思路:

自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法。然后自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值。在Mapper的方法上加入AutoFill注解。

1.创建一个Annotation注解---AutoFill

/**
 * 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
 */
//Target注解指定加上什么上面,Retention注解指定什么时候用
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}

2.定义切面类AutoFillAspect

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect{
    /**
     * 切入点
     * 切入点:对哪些类的哪些方法进行拦截。@Pointcut里面写的是对哪些方法进行拦截
     * 要满足2点:①必须是mapper下的所有类的方法,②还要有AutoFill这个注解。
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..))&& @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    /**
     * 通知(Advice)
    通知是在切点处执行的代码。根据执行时机的不同,通知可以分为以下几种类型:
    1.前置通知(Before Advice):在方法调用之前执行。
    2.后置通知(After Advice):在方法调用之后执行,无论方法是否抛出异常。
    3.返回通知(After Returning Advice):在方法成功返回结果后执行。
    4.异常通知(After Throwing Advice):在方法抛出异常后执行。
    5.环绕通知(Around Advice):在方法调用前后都执行,可以完全控制方法的执行流程。
     */
    @Before("autoFillPointCut()")//前置通知
    public void autoFill(JoinPoint joinPoint) throws Exception {
        log.info("开始进行公共字段的填充......");
        //获取到当前被拦截的方法上的数据库操作类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
        OperationType operationType = autoFill.value();//获得数据库操作类型UPDATE INSERT

        //获取到当前被拦截点的参数--实体对象
        Object[] args = joinPoint.getArgs();
        if (args==null || args.length==0){
            return;
        }
        Object entity = args[0];

        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();


        //根据当前不同操作类型,为对应的属性通过反射来赋值
       //.......
}

3.修改mapper层

最后在mapper层中新增,修改操作上加入@AutoFill(value= OperationType.INSERT)或@AutoFill(value= OperationType.UPDATE)

例如:

 @AutoFill(value = OperationType.INSERT)
    void insert(Category category);

4.Spring AOP实现原理

Spring AOP 主要通过动态代理技术实现切面的织入。具体步骤如下:
1.定义切面类:在切面类中定义通知和切点。
2.配置切面:通过XML配置文件或注解方式配置切面。
3.创建代理对象:Spring容器根据配置信息,使用CGLIBJDK动态代理创建目标对象的代理对象。
4.代理对象调用:当客户端调用目标对象的方法时,实际上是调用了代理对象的方法。代理对象在调用目标方法前后执行相应的通知。

八.动态代理技术

Spring AOP 使用两种主要的动态代理技术来实现切面的织入:
        JDK 动态代理
        CGLIB 动态代理

1.JDK 动态代理

JDK 动态代理基于 Java 的反射机制,适用于实现了接口的类。其主要步骤如下:

1.创建代理对象:通过 java.lang.reflect.Proxy 类创建一个代理对象。
2.实现 InvocationHandler 接口:定义一个实现了 InvocationHandler 接口的类,该类负责处理代理对象的方法调用。
3.方法拦截:在 InvocationHandler invoke 方法中,可以添加前置通知、后置通知等逻辑。

示例代码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkDynamicProxyExample {
    public static void main(String[] args) {
        // 目标对象
        MyService target = new MyServiceImpl();

        // 创建代理对象
        MyService proxy = (MyService) Proxy.newProxyInstance(
            MyService.class.getClassLoader(),
            new Class[]{MyService.class},
            new MyInvocationHandler(target)
        );

        // 调用代理对象的方法
        proxy.doSomething();
    }

    interface MyService {
        void doSomething();
    }

    static class MyServiceImpl implements MyService {
        @Override
        public void doSomething() {
            System.out.println("Doing something...");
        }
    }

    static class MyInvocationHandler implements InvocationHandler {
        private final Object target;

        public MyInvocationHandler(Object target) {
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Before method call");
            Object result = method.invoke(target, args);
            System.out.println("After method call");
            return result;
        }
    }
}

2.CGLIB 动态代理

CGLIB 动态代理通过字节码技术为一个类创建子类,并在子类中拦截所有方法的调用。适用于没有实现接口的类。其主要步骤如下:
1.创建代理对象:通过 net.sf.cglib.proxy.Enhancer 类创建一个代理对象。
2.实现 MethodInterceptor 接口:定义一个实现了 MethodInterceptor 接口的类,该类负责处理代理对象的方法调用。
3.方法拦截:在 MethodInterceptor intercept 方法中,可以添加前置通知、后置通知等逻辑。

示例代码:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibDynamicProxyExample {
    public static void main(String[] args) {
        // 目标对象
        MyService target = new MyServiceImpl();

        // 创建代理对象
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyServiceImpl.class);
        enhancer.setCallback(new MyMethodInterceptor(target));
        MyService proxy = (MyService) enhancer.create();

        // 调用代理对象的方法
        proxy.doSomething();
    }

    interface MyService {
        void doSomething();
    }

    static class MyServiceImpl implements MyService {
        @Override
        public void doSomething() {
            System.out.println("Doing something...");
        }
    }

    static class MyMethodInterceptor implements MethodInterceptor {
        private final Object target;

        public MyMethodInterceptor(Object target) {
            this.target = target;
        }

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("Before method call");
            Object result = method.invoke(target, args);
            System.out.println("After method call");
            return result;
        }
    }
}

九.阿里云oss

具体实现细节看代码,阿里云官方

1.引入阿里云oss依赖

2.配置所需要的相关属性(endpoint、accessKeyId、accessKeySecret和bucketName)

3.创建阿里云oss工具类

4.在传输文件接口使用工具类上传文件

十.Redis

本项目使用一个是存储店铺营业状态,一个是缓存菜品和套餐

1.Redis入门

1.Redis简介

Redis是一个基于内存的key-value结构数据库。读写性能高。因为内存有限所以Redis适合存储热点数据。

2.Redis服务启动与停止

① 服务启动命令:redis-server.exe redis.windows.conf

Redis服务默认端口号为 6379 ,通过快捷键 Ctrl + C 即可停止Redis服务

② 客户端连接命令:redis-cli.exe

 通过redis-cli.exe命令默认连接的是本地的redis服务,并且使用默认6379端口。也可以通过指定如下参数连接:
       -h ip地址
       -p 端口号
       -a 密码(如果需要)

③ 设置Redis服务密码,修改redis.windows.conf

注意
     修改密码后需要重启Redis服务才能生效
     Redis配置文件中 # 表示注释

3.Redis数据类型

Redis存储是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型。

1.字符串string,哈希hash,列表list,集合set,有序集合zset(sorted set)。

2.哈希:在value里面又分为field和value。比较适合存储对象,包括属性和值。

3.列表:类似于一个队列,有顺序,按照插入顺序排序,可以有重复元素。可以存储跟顺序有关系的。

4.集合:无序集合,没有重复元素。可以运算交集或者并集。

5.有序集合:集合中每个元素关联一个分数,根据分数升序排序,没有重复元素。适用场景排行榜,或者投票排名

4.Redis常用命令

字符串操作命令
哈希操作命令
列表操作命令

集合操作命令
有序集合操作命令
通用命令

5.在Java中操作Redis

Redis的Java客户端

Spring Data Redis使用方式

   在本项目(苍穹外卖)中,redis主要用于实现店铺营业状态设置业务(由于本项目的后台管理系统只针对一个商户,即商户-状态对该项目而言只有一条记录,故使用redis去代替mysql来存储这条数据)及缓存菜品和套餐业务(为了避免“出现由于在短时间内大量用户使用小程序时触发大量请求从而给数据库造成压力导致查询性能下降最终降低用户使用体验”的问题,所以这里通过redis来缓存菜品和套餐数据,从而减少数据库查询操作)

2.Redis进阶

这个知识点其实是在黑马商城微服务里面讲到的,如果不带入微服务,那么就还有一点就是redis的三大缓存问题,穿透,击穿,雪崩。

如果大家也是跟着黑马程序员学的话,后面也可以关注一下,我的黑马商城,学成在线的知识点总结,那里会讲到redis的进阶

 十一.微信小程序

1.微信登录

(这里只阐述后端相关的)

1.首先要注册,完善小程序信息

个人方式注册无法开通支付权限。小程序信息完善。开发小程序。提交审核和发布。

开发支持:开发文档,开发者工具,设计指南,小程序体验DEMO。

2.获取APPID和小程序秘钥

在开发管理菜单可以获取APPID和小程序秘钥:

(每个人的id和密钥都是唯一的,要自己保存好,后面需要)

AppID(小程序ID):wx391**********

AppSecret(小程序密钥):f274884*************

3.下载微信开发者工具

4.登录微信开发者工具

5.微信登录流程

小程序登录 | 微信开放文档

点击上面的链接可以进入到如下界面

下面是请求的参数:

微信登录流程:小程序要调用wx.login获取code授权码,然后要调用wx.request()发送code授权码。开发者服务器要向微信接口服务提交appid+appsecret+code,然后就可以获得session_key(会话密钥)和openid。然后开发者服务器可以自定义登录状态,产生一个token令牌,然后返回自定义登录状态。小程序可以把token令牌进行存储。然后wx.request可以法器业务请求,开发者服务器可以解析token,最终返回数据。

简而言之,小程序给后端发送授权码code,然后后端去调微信的接口服务,后端再给用户返回令牌,令牌里包含用户的唯一标识。

 6.数据库存储登录信息

7.创建JWT令牌

登录成功后会根据用户的openId来作为负载创建JWT令牌

(也需要在单独声明一个user的JWT令牌验证拦截器)

2.HttpClient

在微信登录实现的时候,后端需要跟微信服务器连接,这个时候就需要需要HttpClient

也就是获取微信用户唯一openid

1.实现HttpClient

1.引入依赖
<dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.13</version>
</dependency>

2.创建HttpClient工具类

此项目中只使用到了get请求,总结步骤:

1.创建HttpClient对象

2.拼装url,这里的url需要拼装的是微信提供的微信服务接口地址

https://api.weixin.qq.com/sns/jscode2session

在加上需要的请求参数

appid + secret + js_code + grant_type

3.创建GET请求

4.发送请求

5.判断响应状态

6.返回结果

/**
 * Http工具类
 */
public class HttpClientUtil {

    static final  int TIMEOUT_MSEC = 5 * 1000;

    /**
     * 发送GET方式请求
     * @param url
     * @param paramMap
     * @return
     */
    public static String doGet(String url,Map<String,String> paramMap){
        // 创建Httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        String result = "";
        CloseableHttpResponse response = null;

        try{
            URIBuilder builder = new URIBuilder(url);
            if(paramMap != null){
                for (String key : paramMap.keySet()) {
                    builder.addParameter(key,paramMap.get(key));
                }
            }
            URI uri = builder.build();

            //创建GET请求
            HttpGet httpGet = new HttpGet(uri);

            //发送请求
            response = httpClient.execute(httpGet);

            //判断响应状态
            if(response.getStatusLine().getStatusCode() == 200){
                result = EntityUtils.toString(response.getEntity(),"UTF-8");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                response.close();
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return result;
    }

    
}

3.微信支付

1.接入流程

(需要营业执照才能接入,才能真正实现支付)

 2.微信小程序支付时序图

这节涉及到2个问题:1.调用过程如何保证数据安全?2.微信后台如何调用到商户系统?

1.保证数据安全:微信支付涉及到数据的传输,为了保证数据的安全,所以需要对数据进行加密和解密,需要用到证书。

需要下面2个文件(要求小程序是企业认证):

获取微信支付平台证书文件:apiclient_key.pem。

商户私钥文件:wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pem

2.微信后台调用商户系统:当前商户系统的ip是本机的ip,仅仅只是局域网内的ip地址,要求获得公网ip,可以使用内网穿透技术解决。

这里我们使用的是cpolar

3.Cpolar内网穿透

1.下载Cpolar

2.到官网获取隧道Authtoken

3.输入如下代码,粘贴token即可

4.会生成一个配置文件

5.输入代码:cpolar.exe http 8080

6.获取临时域名并访问

可以通过域名+doc.html进行访问

 需要注意的是,临时域名的时间是有限的,所以过段时间是需要重新申请的

4.JSAPI下单

1.商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识后再按Native、JSAPI、APP等不同场景生成交易串调起支付。

文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
请求地址:https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

2.通过JSAPI下单接口获取到发起支付的必要参数prepay_id,然后使用微信支付提供的小程序方法调起小程序支付。

文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml
请求:调用wx.requestPayment(OBJECT)发起微信支付

十二.SpringCache

本项目就是使用SpringCache把缓存存入Redis中

菜品和套餐存储在数据库中,如果短期内有大量的人查询会导致数据库压力过大,用户体验不佳。现在把商品数据缓存到Redis中。

以后用户再查询菜品和套餐的时候,首先查询Redis中是否存在,如果不存在,那么再去查询mysql

使用前,要确保Redis连接正常

1.SpringCache实现

1.引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2.在启动类上加上@EnableCaching        

3.在controller层需要进行缓存的方法上加上对应的注解

@CachePut(cacheNames = "userCache", key = "#user.id") // 若使用Spring Cache缓存数据, key的生成:userCache::1(1是通过 #user.id 动态获取到的)

@Cacheable(cacheNames = "userCache", key = "#id") // key的生成:userCache::1(1是通过 #id 动态获取到的)

@CacheEvict(cacheNames = "userCache", key = "#id") // key的生成:userCache::1(1是通过 #id 动态获取到的)

@CacheEvict(cacheNames = "userCache", allEntries = true) // 会匹配到userCache下所有键值对,会删除所以

2.SpringCache原理

Spring Cache底层基于代理技术。一旦加入Spring Cache提供的注解后,Spring Cache就会为当前类创建一个代理对象在请求对应方法前,实际上请求会先进入到代理对象,在代理对象中先查询redis等缓存。

十三. Spring Task

1.介绍

Spring Task 是Spring框架提供的任务调度工具(Spring Task 本身也属于Spring家族当中的一个框架),Spring Task是一个非常小的框架,小到都没有自己单独的jar包,都是集成在了spring-context包中,可以按照约定的时间自动执行某个代码逻辑。

定位:定时任务框架
作用:定时自动执行某段Java代码
应用场景:只要是需要定时处理的场景都可以使用Spring Task,如:信用卡每月还款提醒、银行贷款每月还款提醒火车票售票系统处理未支付订单及入职纪念日为用户发送通知等

在本项目中是定时处理超时订单

2.cron表达式

cron表达式在线生成器(https://cron.qqe2.com/

cron表达式是一个字符串,通过cron表达式可以定义任务触发的时间。

构成规则:分为6或7个域,由空格分隔开,每个域代表一个含义。

每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)

3.创建定时任务

①导入maven坐标,spring-context(已存在)

②启动类添加注解@EnableScheduling开启任务调度

③自定义定时任务类,加上@Component注解

十四.WebSocket

1.介绍 

WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信(主要体现在浏览器和服务器可以实现双向数据传输。说的更直白些,就是浏览器可以向服务器来传输数据;同时,服务器也可以主动向浏览器来传输数据,他们之间是双向的)——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输

应用:视频弹幕,网页聊天(聊天窗口和客服聊天),体育实况更新,股票基金报价实时更新。

本项目使用WebSocket实现来单提醒、客户催单

2.WebSocket使用

1.导入WebSocket的maven坐标

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2.导入WebSocket服务端组件WebSocketServer(名称随意),用于和客户端通信

WebSocket会把与之连接的客户端的sid都存入Map中

WebSocket可以根据不同的客户端sid来发送给不同的客户端

/**
 * WebSocket服务
 */
@Component
@ServerEndpoint("/ws/{sid}")//指定了 WebSocket 的访问路径,其中 {sid} 是一个路径参数,代表会话 ID。
public class WebSocketServer {

    //存放会话对象
    private static Map<String, Session> sessionMap = new HashMap();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        System.out.println("客户端:" + sid + "建立连接");
        sessionMap.put(sid, session);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        System.out.println("收到来自客户端:" + sid + "的信息:" + message);
    }

    /**
     * 连接关闭调用的方法
     *
     * @param sid
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        System.out.println("连接断开:" + sid);
        sessionMap.remove(sid);
    }

    /**
     * 群发
     *
     * @param message
     */
    public void sendToAllClient(String message) {
        Collection<Session> sessions = sessionMap.values();
        for (Session session : sessions) {
            try {
                //服务器向客户端发送消息
                session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

3.导入配置类WebSocketConfiguration,注册WebSocket的服务端组件

/**
 * WebSocket配置类,用于注册WebSocket的Bean
 */
@Configuration
public class WebSocketConfiguration {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

4.导入定时任务类WebSocketTask,定时向客户端推送数据(方便测试)


@Component
public class WebSocketTask {
    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 通过WebSocket每隔5秒向客户端发送消息
     */
    @Scheduled(cron = "0/5 * * * * ?")
    public void sendMessageToClient() {
        webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
    }
}

3.补充

既然WebSocket支持双向通信,功能看似比HTTP强大,那么我们是不是可以基于WebSocket开发所有的业务功能?
WebSocket缺点:

① 服务器长期维护长连接需要一定的成本;

② 各个浏览器支持程度不一;

③ WebSocket 是长连接,受网络限制比较大,需要处理好重连

结论:WebSocket并不能完全取代HTTP,它只适合在特定的场景下使用

十五.Apache POI

1.介绍

Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是,我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。

为什么要在Java程序中操作Excel文件呢?
答:Apache POI 的应用场景:① 银行网银系统导出交易明细(写);② 各种业务系统导出Excel报表(写);③ 批量导入业务数据(读)(这里的读写均针对文件而言)

2.Apache POI使用

本项目运用---导出30天的运营数据

1.引入依赖

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
</dependency>

2.入门案例

/**
 * 使用POI操作Excel文件
 */
public class POITest {

    /**
     * 通过POI创建Excel文件并且写入文件内容
     */
    public static void write() throws Exception{
        //在内存中创建一个Excel文件
        XSSFWorkbook excel = new XSSFWorkbook();
        //在Excel文件中创建一个Sheet页,使用代码方式创建Excel文件时默认不会自动创建Sheet页,所以需要手动添加一个Sheet页
        XSSFSheet sheet = excel.createSheet("info");
        //在Sheet中创建行对象,rownum编号从0开始
        XSSFRow row = sheet.createRow(1);
        //创建单元格并且写入文件内容
        row.createCell(1).setCellValue("姓名");
        row.createCell(2).setCellValue("城市");

        //创建一个新行
        row = sheet.createRow(2);
        row.createCell(1).setCellValue("张三");
        row.createCell(2).setCellValue("北京");

        row = sheet.createRow(3);
        row.createCell(1).setCellValue("李四");
        row.createCell(2).setCellValue("南京");

        //通过输出流将内存中的Excel文件写入到磁盘
        FileOutputStream out = new FileOutputStream(new File("D:\\info.xlsx"));
        excel.write(out);

        //关闭资源
        out.close();
        excel.close();
    }


    /**
     * 通过POI读取Excel文件中的内容
     * @throws Exception
     */
    public static void read() throws Exception{
        // 获取要读取的Excel文件的输入流对象
        InputStream in = new FileInputStream(new File("D:\\info.xlsx"));

        //读取磁盘上已经存在的Excel文件
        XSSFWorkbook excel = new XSSFWorkbook(in);
        //读取Excel文件中的第一个Sheet页
        XSSFSheet sheet = excel.getSheetAt(0);

        //获取Sheet中最后一行有内容的行号
        int lastRowNum = sheet.getLastRowNum();

        for (int i = 1; i <= lastRowNum ; i++) {
            //获得某一行
            XSSFRow row = sheet.getRow(i);
            //获得单元格对象
            String cellValue1 = row.getCell(1).getStringCellValue();
            String cellValue2 = row.getCell(2).getStringCellValue();
            System.out.println(cellValue1 + " " + cellValue2);
        }

        //关闭资源
        in.close();
        excel.close();
    }

    public static void main(String[] args) throws Exception {
        // 测试写方法
        write();
        // 测试读方法
        read();
    }
}

十六.细节补充

1.数据库采用MD5对用户密码加密:需要注意的是,MD5加密是不可逆的。也就是说我们只能由一个明文进行MD5加密然后得到一个密文,而不能通过一个密文算出来原本的明文是什么。即,这个过程是单向的,只能从左到右,而不能从右到左。此时想要进行密码比对只能将一个明文进行加密处理再跟另一个密文去进行比对

2.项目中使用到的相关注解,很多用法相识,但是需要细分,深挖

3.mapper.xml中的sql语句,相关条件,这些也需要细看一下

总体来说单体项目涉及到的知识点都相对基础,但是很多基础是需要我们自己去深挖的,至于需要深挖哪些,这个就要看自己的敏感程度了,把这个单体项目吃透,再去复习微服务项目的时候就不会显得很吃力

### 苍穹外卖公司面试经验 对于苍穹外卖公司的技术面试,重点在于展示候选人对核心技术的理解和实际应用的能力。在面试过程中,面试官更关注的是候选人在项目中所运用的技术栈及其背后的设计思路和技术实现细节[^1]。 #### 技术考察点 - **Spring Boot核心原理** Spring Boot作为微服务架构中的重要框架之一,在面试中占据着举足轻重的地位。理解其自动配置机制、启动流程以及如何简化开发工作量等内容显得尤为重要。通过参与像“苍穹外卖后台管理版”这样的开源项目可以加深对此类概念的认识并积累实战经验。 - **数据库设计与优化** 数据库操作频繁且复杂的应用场景下,合理的表结构设计、索引创建策略等都是不可忽视的知识领域。能够清晰阐述自己在过去工作中遇到的数据层挑战及解决方案会大大加分。 - **分布式系统的理解和实践经验** 当前互联网企业大多采用分布式的部署方式来应对高并发访问需求。因此熟悉诸如负载均衡、缓存机制(Redis)、消息队列(RabbitMQ/Kafka)等相关组件的工作原理,并能分享具体案例说明这些工具是如何帮助解决性能瓶颈问题是很有必要的。 ```python from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' ``` 此代码片段展示了简单的Flask Web应用程序搭建方法,虽然不是直接针对苍穹外卖的具体技术栈,但对于快速构建原型或小型API接口非常有用。 #### 常见问题 - 如何解释你在之前负责过的某个大型项目的整体架构? - 描述一次你主导完成的功能模块开发经历,包括面临的主要困难及最终采取的有效措施。 - 如果让你重新设计现有系统的一部分功能你会怎么做?请给出理由和支持论据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值