瑞吉外卖知识点总结(2)

1. 手机验证码功能实现

1.1 业务流程:

(1)业务端输入手机号,点击发送
(2)后台接收请求,验证手机号,生成验证码。
这里要注意验证码是在后台生成好的。我原来一直以为手机里的验证码是运营商生成的,原来是错误的。
(3)将生成验证码并通过阿里云接口向指定的手机号发送。
(4)手机接收到验证码后,填写到前端,登录
(5)后台根据前端填写的验证码和手机号和后端去匹配,如果一致就通过,否则,返回错误信息。

1.2 依赖包

        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.5.16</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
            <version>2.1.0</version>
        </dependency>

1.3 工具类

阿里短信的工具类,最后一个param就是传入后台生成的验证吗。

package com.guigutool.reggie.utils;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;

/**
 * 短信发送工具类
 */
public class SMSUtils {

	/**
	 * 发送短信
	 * @param signName 签名
	 * @param templateCode 模板
	 * @param phoneNumbers 手机号
	 * @param param 参数
	 */
	public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
		DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
		IAcsClient client = new DefaultAcsClient(profile);

		SendSmsRequest request = new SendSmsRequest();
		request.setSysRegionId("cn-hangzhou");
		request.setPhoneNumbers(phoneNumbers);
		request.setSignName(signName);
		request.setTemplateCode(templateCode);
		request.setTemplateParam("{\"code\":\""+param+"\"}");
		try {
			SendSmsResponse response = client.getAcsResponse(request);
			System.out.println("短信发送成功");
		}catch (ClientException e) {
			e.printStackTrace();
		}
	}

}

1.4 登录案例实现

(1) 点击发送验证码按钮,后端的处理

    @Autowired
    private RedisTemplate redisTemplate;

    @PostMapping("/sendMsg")
    public R<String> sendMsg(@RequestBody User user, HttpSession session){
        //获取手机号
        String phone = user.getPhone();
        if(StringUtils.isNotEmpty(phone)){
            //生成随机的4位验证吗
            String code = ValidateCodeUtils.generateValidateCode(4).toString();
            log.info("code:{}", code);
            //调用阿里云提供的短信API完成发送短信
            SMSUtils.sendMessage("Reggie","",phone,code);
            //需要将生成的验证码保存到Session
            //session.setAttribute(phone,code);
            //将生成的验证码保存到Redis中,并且设置有效期为5分钟
            redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);
            return R.success("验证码发送成功");
        }
        return R.error("验证码发送失败");

    }

(2) 填写验证码后,登录处理

 /**
     * 移动端用户登录
     * @param map
     * @param session
     * @return
     */
    @PostMapping("/login")
    public R<User> login(@RequestBody Map map, HttpSession session){
        String phone = map.get("phone").toString();
        String code = map.get("code").toString();

//        Object codeInSession = session.getAttribute(phone);
        //从redis中获取缓存的验证码
        Object codeInSession = redisTemplate.opsForValue().get(phone);
        if(Objects.nonNull(codeInSession) && codeInSession.equals(code)){

            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getPhone,phone);
            User user = userService.getOne(queryWrapper);
            if(Objects.isNull(user)){
                user = new User();
                user.setPhone(phone);
                user.setStatus(1);
                userService.save(user);
            }

            //登录成功
            session.setAttribute(SystemConstant.MOBILE_USER_ID,user.getId());
            //如果用户登录成功,则删除Redis中缓存的验证码
            redisTemplate.delete(phone);
            return R.success(user);
        }

        return R.error("登录失败");

    }

2. 缓存

2.1 Cache缓存

SpringCache是一个基于注解的缓存功能,只要简单的加一个注解,就能实现缓存功能。
SpringCache也提供了一层抽象接口,底层可以切换不同的cache实现类,接口名称是CacheManager。
CacheManager是Spring提供的各种缓存技术抽象接口。
以下是一些一些不同的实现类:

CacheManager描述
EhCacheCacheManager使用EhCache作为缓存技术
GuavaCacheManager使用Google的GuavaCache作为缓存技术
RedisCacheManager使用Redis作为缓存技术

2.2 SpringCache缓存使用

(1)默认配置
只要加入spring-boot-starter-web,就会存在SpringCache的配置。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>compile</scope>
        </dependency>

默认有以下4个实现类,所以可以不做任何改动。
在这里插入图片描述

(2) 在启动应用主程序添加@EnableCaching注解
在这里插入图片描述

(3) 注入

    //Ctrl+Alt+鼠标左点击,可查看该接口的实现类
    @Autowired
    private CacheManager cacheManager;

(4)注解说明

注解说明用途
@EnableCaching开启缓存注解功能全部配置,在启动app类上注解
@Cacheable在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中查询操作
@CachePut将方法的返回值放到缓存中保存或者修改操作
@CacheEvict将一条或者多条记录从缓存中删除删除操作

(5)使用案例
定义缓存的名称

private final String cacheName = "userCache";  

保存操作

    /**
     * cachePut:将方法返回值放入缓存
     * value: 缓存的名称,每个缓存名称下可以有多个key
     * key:缓存的key, 这个取自于方法的返回值
     * 例如:这里的key使用的是返回的user对象的id
     */
    @CachePut(value=cacheName, key = "#result.id")
    @PostMapping
    public User save(User user){
        userService.save(user);
        return user;
    }

删除操作

    /**
     * 这里的key使用的是传入的参数
     * @param id
     */
    @CacheEvict(value = cacheName, key="#id")
    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id){
        userService.removeById(id);
    }

修改操作

//    @CacheEvict(value = cacheName, key="#p0.id")       (1)根据接收的参数设置的key
//    @CacheEvict(value = cacheName, key="#user.id")     (2)根据接收的参数设置的key
//    @CacheEvict(value = cacheName, key="#root.args[0].id")  (3)根据接收的参数设置的key
//    @CacheEvict(value = cacheName, key="#result.id")   (4)根据返回的值设置的key
    @CacheEvict(value = cacheName, key="#result.id")
    @PutMapping
    public User update(User user){
        userService.updateById(user);
        return user;
    }

查询操作
condition 满足条件时,才缓存

    /**
     * condition: 条件,满足条件时,才缓存数据
     * unless: 满足条件,不缓存  #注意这里会发生缓存穿透,就是当用户查询的数据不存在时,会一直调用sql,如果有太多的请求请数据库发送sql语句,也会加重数据库的负载
     */
    @Cacheable(value = cacheName,key="#id", condition = "#result != null")
    @GetMapping("/{id}")
    public User getById(@PathVariable Long id){
        User user = userService.getById(id);
        return user;
    }

unless: 满足条件时,不缓存

    /**
     * condition: 条件,满足条件时,才缓存数据
     * unless: 满足条件,不缓存  #注意这里会发生缓存穿透,就是当用户查询的数据不存在时,会一直调用sql,如果有太多的请求请数据库发送sql语句,也会加重数据库的负载
     */
    @Cacheable(value = cacheName,key="#id", unless = "#result == null")
    @GetMapping("/{id}")
    public User getById(@PathVariable Long id){
        User user = userService.getById(id);
        return user;
    }

    @Cacheable(value = cacheName, key = "#user.id+':'+#user.name")
    @GetMapping("/list")
    public List<User> list(User user){
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Objects.nonNull(user.getName()),User::getName, user.getName());
        List<User> userList = userService.list(wrapper);
        return userList;
    }

2.3 SpringRedis缓存

添加依赖:

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

在这里插入图片描述

yml添加:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
  cache:
    redis:
      time-to-live: 1800000 #设置缓存有效期

从SpringCache缓存切换到Redis缓存,只需要修改配置即可。

3. 接口文档Swagger

3.1 增强版本

使用增强功能knife4j
(1) Pom配置

        <!--  增强swagger-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

(2) WebMvc配置

/**
 * @Author: KingWang
 * @Date: 2023/3/7
 * @Desc:
 **/
@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始静态资源映射..");
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");

        //swagger文档需要添加下面2行
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }


    @Bean
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.guigutool.reggie.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    public ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("瑞吉外卖")
                .version("1.0")
                .description("瑞吉外卖接口文档")
                .build();
    }
}

(3) 过滤中放行静态资源访问权限
LoginCheckFilter中的doFilter方法,如果不放行,则需要登录后才可以查看doc.html。

@Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

        log.info("拦截到请求:{}", request.getRequestURI());
        //1. 获取本次请求的URI
        String requestURI = request.getRequestURI();

        //2.定义不需要处理的请求路径
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**",
                "/common/**",
                "/user/sendMsg", //移动端发送短信
                "/user/login", //移动端登录
               
                "/doc.html",  //生成swagger api文件,这里不放行,在登录后,也可以查看
                "/webjars/**",
                "/swagger-resources",
                "/v2/api-docs"
        };
        
        //其他内容省略...
     

    }

(4) 接口文档查看
地址:http://localhost:8080/doc.html#/home
在这里插入图片描述

3.2 常用Swagger注解

注解说明
@Api用于请求的类上,例如Controller,表示对类的说明
@ApiModel用在类上,通常是实体类,表示一个返回响应数据的信息
@ApiModelProperty用在属性上,描述响应类的属性
@ApiOperation用在请求的方法上,说明方法的用途,作用
@ApiImplicitParams用在请求的方法上,表示一组参数说明
@ApiImplicitParam用在@ApiImplicitParams注解中,指定一个请求参数的各个方面

3.3 Swagger注解实例

(1)实体类注解

/**
 * 套餐
 */
@Data
@ApiModel("套餐")
public class Setmeal implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("主键Id")
    private Long id;


    @ApiModelProperty("分类Id")
    private Long categoryId;


    @ApiModelProperty("套餐名称")
    private String name;


    @ApiModelProperty("套餐价格")
    private BigDecimal price;


    @ApiModelProperty("状态 0:停用 1:启用")
    private Integer status;


    @ApiModelProperty("编码")
    private String code;


    @ApiModelProperty("描述信息")
    private String description;


    @ApiModelProperty("图片")
    private String image;


    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;


    @TableField(fill = FieldFill.INSERT)
    private Long createUser;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;


    //是否删除
    private Integer isDeleted;
}

(2)Controller注解

/**
 * @Author: KingWang
 * @Date: 2023/3/14
 * @Desc:
 **/
@RequestMapping("/setmeal")
@Slf4j
@RestController
@Api(tags="套餐相关接口")
public class SetmealController {

    private final String cacheName = "setmealCache";

    @Autowired
    private SetmealDishService setmealDishService;
    @Autowired
    private SetmealService setmealService;
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 新增套餐
     * @param setmealDto
     * @return
     */
    @ApiOperation(value = "新增套餐")
    @PostMapping
    @CacheEvict(value = cacheName, allEntries = true)
    public R<String> save(@RequestBody SetmealDto setmealDto){

        setmealService.saveWithDish(setmealDto);
        Set keys = redisTemplate.keys("setmeal:*");
        redisTemplate.delete(keys);

        return R.success("新增套餐成功");
    }

    @ApiOperation(value = "套餐分页查询")
    @GetMapping("/page")
    public R<Page> page(String name, int page,int pageSize){
        Page<Setmeal> pageInfo = new Page(page,pageSize);

        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(null != name, Setmeal::getName,name);
        setmealService.page(pageInfo, queryWrapper);

        Page<SetmealDto> setmealDtoPage = new Page<>(page,pageSize);
        BeanUtils.copyProperties(pageInfo,setmealDtoPage,"records");

        List<SetmealDto> setmealDtoList =  pageInfo.getRecords().stream().map(x-> {
            SetmealDto setmealDto = new SetmealDto();
            BeanUtils.copyProperties(x,setmealDto);
            Category category = categoryService.getById(x.getCategoryId());
            setmealDto.setCategoryName(category.getName());
            return setmealDto;
        }).collect(Collectors.toList());
        setmealDtoPage.setRecords(setmealDtoList);

        return R.success(setmealDtoPage);
    }

    /**
     * 删除套餐
     * @param ids
     * @return
     *
     * allEntries=true 清理指定key下的所有数据
     */
    @ApiOperation(value = "删除套餐")
    @DeleteMapping
    @CacheEvict(value = cacheName, allEntries = true)
    public R<String> delete(@RequestParam List<String> ids){
        log.info("delete ids: {}",ids);
        setmealService.removeWithDish(ids);
        Set keys = redisTemplate.keys("setmeal:*");
        redisTemplate.delete(keys);
        return R.success("套餐删除成功");
    }

    @ApiOperation(value = "套餐列表查询")
    @Cacheable(value = cacheName,key = "#setmeal.categoryId+':'+ #setmeal.status")
    @GetMapping("/list")
    public R<List<Setmeal>> list(Setmeal setmeal){
        List<Setmeal> setmealList = null;
        String key = "setmeal:" + setmeal.getCategoryId();
        setmealList = (List<Setmeal>)redisTemplate.opsForValue().get(key);
        if(Objects.nonNull(setmealList)){
            return R.success(setmealList);
        }
        LambdaQueryWrapper<Setmeal> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Objects.nonNull(setmeal.getId()),Setmeal::getId,setmeal.getId());
        wrapper.eq(Objects.nonNull(setmeal.getCategoryId()),Setmeal::getCategoryId,setmeal.getCategoryId());
        wrapper.eq(Objects.nonNull(setmeal.getStatus()),Setmeal::getStatus,setmeal.getStatus());
        wrapper.orderByDesc(Setmeal::getUpdateTime);
        setmealList = setmealService.list(wrapper);

        redisTemplate.opsForValue().set(key, setmealList);

        return R.success(setmealList);
    }
}


(3)工具类R注解

@Data
@ApiModel("返回结果")
public class R<T> implements Serializable {

    @ApiModelProperty("编码")
    private Integer code; //编码:1成功,0和其它数字为失败

    @ApiModelProperty("返回信息")
    private String msg; //返回的信息

    @ApiModelProperty("数据")
    private T data; //数据

    @ApiModelProperty("动态数据")
    private Map map = new HashMap(); //动态数据

    public static <T> R<T> success(T object) {
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    }


    public static <T> R<T> error(String msg) {
        R r = new R();
        r.msg = msg;
        r.code = 0;
        return r;
    }

    public R<T> add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }

}

文档展示:
在这里插入图片描述

4. 前后端分离部署

4.1 nginx部署前端项目

nginx配置:

  server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html/dist;
            index  index.html;
        }

        location ^~ /api/ {
          rewrite ^/api/(.*)$ /$1 break;
          proxy_pass http://192.168.80.250:8080;
        }

        #error_page  404              /404.html;

从前端访问80页面后,代理访问到后端http://192.168.80.250:8080的地址请求

4.2 shell脚本一键部署后端项目

后端一键部署流程,git拉取 -> 编译,打包 -> 启动
脚本名称命名为bootStart.sh.
注意不能带reggie这个名称字样,否则下面判断进程是否存活时,会找到脚本自己。

#!/bin/sh

echo =================================
echo  $(date "+%Y-%m-%d %H:%M:%S") 自动化部署脚本启动
echo =================================

echo $(date "+%Y-%m-%d %H:%M:%S") 停止原来运行中的工程
APP_NAME=reggie

tpid=`ps -ef|grep ${APP_NAME}|grep -v grep|grep -v $$|awk '{print $2}'`
if [[ ${tpid} != "" ]]; then
    echo '当前进程号:' $tpid
    echo 'Stop Process...'
    kill -15 $tpid
fi
sleep 3

tpid=`ps -ef|grep ${APP_NAME}|grep -v grep|grep -v $$|awk '{print $2}'`

if [[ ${tpid} != "" ]]; then
    echo '当前进程号:' $tpid '任然存在'
    echo 'Kill Process!'
    kill -9 $tpid
else
    echo 'Stop Success!'
fi

echo $(date "+%Y-%m-%d %H:%M:%S") 准备从Git仓库拉取最新代码
cd /opt/local/reggie

echo $(date "+%Y-%m-%d %H:%M:%S") 开始从Git仓库拉取最新代码
git pull
echo currTime 代码拉取完成

echo $(date "+%Y-%m-%d %H:%M:%S") 开始打包
output=`mvn clean package -Dmaven.test.skip=true`

cd target

echo $(date "+%Y-%m-%d %H:%M:%S") 启动项目
nohup java -jar reggie-1.0-SNAPSHOT.jar &> reggie.log &
echo $(date "+%Y-%m-%d %H:%M:%S") 项目启动完成

启动示例:
在这里插入图片描述

5. Git常用操作

5.1 Git本地与远程建立连接

(1) 项目根目录添加.gitignore文件
gitignore文件可以指定在提交到远程仓库时,不需要提交的文件。

# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

.idea
/test/
/target/
/target/
/target/classes/


(2)本地创建git仓库
在这里插入图片描述

(3)提交文件
添加gitignore文件

git add .gitignore
git commit

(4) 在远程gitee创建一个项目地址
在这里插入图片描述

(5)推送到远程仓库
在这里插入图片描述

点开Define remote,输入在gitee创建的库的地址,
在这里插入图片描述

在这里插入图片描述

5.2 分支

分支创建
在这里插入图片描述

5.3 合并

将develop分支开发好的内容合并到当前master分支上
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

硅谷工具人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值