欢迎光临
我们一直在努力

苍穹外卖复习

jwt的登录验证

1.JwtProperties(jwt的基本配置项):

@Component
@ConfigurationProperties(prefix = "sky.jwt")
@Data
public class JwtProperties {

/**
* 管理端员工生成jwt令牌相关配置
*/

private String adminSecretKey;
private long adminTtl;
private String adminTokenName;

/**
* 用户端微信用户生成jwt令牌相关配置
*/

private String userSecretKey;
private long userTtl;
private String userTokenName;

}

在配置文件中设置好

sky:
jwt:
# 设置jwt签名加密时使用的秘钥
adminsecretkey: itcast
# 设置jwt过期时间
adminttl: 7200000
# 设置前端传递过来的令牌名称
admintokenname: token
usersecretkey: itheima
userttl: 7200000
usertokenname: authentication

2.JwtTokenInterceptor(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 {

System.out.println("当前线程的id"+ Thread.currentThread().getId());
//判断当前拦截到的是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;
}
}
}

image-20251219185810255

JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);

这行代码的作用是验证并解析JWT令牌:

  • JwtUtil.parseJWT():调用JWT工具类的解析方法,该方法会验证令牌的签名有效性并解码内容 。
  • jwtProperties.getAdminSecretKey():获取用于验证签名的密钥,这个密钥必须与生成令牌时使用的密钥一致 。
  • token:待解析的JWT令牌字符串,通常从HTTP请求头中获取。
  • Claims claims:解析成功后返回的声明对象,包含了JWT负载(Payload)中的所有数据 。Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
  • 这行代码从解析后的声明中提取具体的用户标识信息:
    • claims.get(JwtClaimsConstant.EMP_ID):从Claims对象中获取键为EMP_ID的声明值,这通常是在用户登录生成JWT时存入的员工ID 。
    • .toString():将获取到的对象转换为字符串,确保类型安全。
    • Long.valueOf():把字符串转换为Long类型的员工ID,便于后续在业务中使用。

其中的parseJWT是token解密:

/**
* 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;
}

生成jwt:

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

3.在代码中使用jwt:

@PostMapping("/login")
@ApiOperation(value = "员工登录")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
log.info("员工登录:{}", employeeLoginDTO);

Employee employee = employeeService.login(employeeLoginDTO);

//登录成功后,生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);

EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
.id(employee.getId())
.userName(employee.getUsername())
.name(employee.getName())
.token(token)
.build();

return Result.success(employeeLoginVO);
}

在webConfiguration中注册自定义拦截器:

/**
* 注册自定义拦截器
*
* @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");
}

新增菜品

在新增菜品中用到了集合遍历,下面这三种遍历效果一样

传统 for 循环

for (DishFlavor flavor : flavors) { flavor.setDishId(dishId); }

语法冗长,但逻辑清晰

Stream API

flavors.stream().peek(flavor -> flavor.setDishId(dishId)).collect(…)

适合链式操作,但需注意副作用(如修改原集合)

peek的作用
对流中的每个 flavor对象执行 setDishId(dishId)操作,为每个口味对象设置关联的菜品 ID。
关键点:peek是中间操作,不会终止流,需配合 collect等终止操作触发执行。
链式操作流程

流生成:flavors.stream()将集合转为流。

副作用操作:peek修改元素属性(设置 dishId)。

终止操作:collect(…)收集结果(如转 List或 Map)。

方法引用

flavors.forEach(flavor -> flavor.setDishId(dishId));

与 Lambda 类似,但更简洁

在新增菜品的时候的xml语句

1.插入数据,并且设置自增键

核心机制

  • 数据库自动生成主键 当数据库表的主键字段设置为 自增(AUTO_INCREMENT) 时(如 MySQL 的 id INT AUTO_INCREMENT),执行 INSERT语句时数据库会自动生成唯一的主键值。 无需手动插入:你不需要在 SQL 中显式为 id字段赋值(如 id=#{id}),数据库会自动填充。
  • MyBatis 回填主键到实体类 useGeneratedKeys="true":启用 MyBatis 的 主键回填功能,通过 JDBC 的 getGeneratedKeys()方法获取数据库生成的主键。 keyProperty="id":将获取到的主键值赋给 Java 对象的 id属性(需与实体类属性名一致)。
  • <!– useGeneratedKeys:true 表示获取主键值

    keyProperty=“id” 表示将主键值赋给id属性–>

    insert into dish
    (status, name, category_id, price, image, description, create_time, update_time, create_user,update_user) values
    (#{status}, #{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime},#{createUser}, #{updateUser})

    2:批量插入数据

    collection=“flavors”
    功能:指定要迭代的集合来源

    item=“dishFlavor”
    功能:定义集合中每个元素的别名。

    separator=“,”
    功能:指定元素之间的分隔符。

    insert into dish_flavor(dish_id, name, value) values

    ​ (#{dishFlavor.dishId},#{dishFlavor.name},#{dishFlavor.value})

    菜品的分页查询

    除了DTO和VO外,分页查询还需要的是一个PageResult来获取一个总的记录数和当前页的数据集合:

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class PageResult implements Serializable {

    private long total; //总记录数

    private List records; //当前页数据集合

    }

    @GetMapping("/page")
    @ApiOperation("菜品分页查询")
    public Result<PageResult>page (DishPageQueryDTO dishPageQueryDTO) {
    log.info("菜品分页查询");
    PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
    return Result.success(pageResult) ;
    }

    实际实现分页查询:

    **PageHelper.startPage:**在分页查询中使用到了PageHelper.startPage来设置分页参数并启动分页拦截器。

    分页参数即当前页码和当前要返回多少数据,这是用前端给我们的DTO中获取的

    **Page:**这是内置的类型,用于分页,含有方法来帮助获得总记录数和数据集合。

    public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
    PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());
    Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
    return new PageResult(page.getTotal(),page.getResult());
    }

    查询的xml语句:

    作用:

    • 查询 dish表所有字段(d.*)和 category表的 name字段(别名 categoryName)。
    • 左外连接:即使 dish表的 category_id在 category表中无匹配记录,仍会返回 dish数据(categoryName为 NULL)。

    <select id="pageQuery" resultType="com.sky.vo.DishVO">
    select d.* , c.name as categoryName from dish d left outer join category c on d.category_id = c.id
    <where>
    <if test="name != null">
    and d.name like concat('%',#{name},'%')

    </if>
    <if test="categoryId != null">
    and d.category_id = #{categoryId}
    </if>
    <if test="status != null">
    and d.status = #{status}
    </if>
    </where>
    order by d.create_time desc
    </select>

    删除菜品

    测功能较为简单

    单个删除:

    @Delete(“delete from dish where id = #{id}”)

    批量删除:

    open和close是在最后的结果上加上左括号和右括号

    <delete id="deleteByDishIds">
    delete from dish_flavor where dish_id in
    <foreach collection="dishIds" item="dishids" separator="," open="(" close=")">
    #{dishids}
    </foreach>
    </delete>

    修改菜品

    正常的修改菜品

    <update id="update">
    update dish
    <set>
    <if test="name != null">name = #{name}, </if>
    <if test="categoryId != null">category_id = #{categoryId}, </if>
    <if test="price != null">price = #{price}, </if>
    <if test="image != null">image = #{image}, </if>
    <if test="description != null">description = #{description}, </if>
    <if test="status != null">status = #{status}, </if>
    <if test="updateTime != null">update_time = #{updateTime}, </if>
    <if test="updateUser != null">update_user = #{updateUser}, </if>
    </set>
    where id = #{id}
    </update>

    在修改对应的口味的时候,执行删除和插入口味的操作

    Redis

    在java中使用redis

    1.导入Spring Data Redis 的maven坐标
    2.配置Redis数据源
    3.编写配置类,创建RedisTemplate对象
    4.通过RedisTemplate对象操作Redis

    利用RedisTemplate对象模板来进行编写redis:

    @Configuration
    public class RedisConfiguration {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);

    // 设置Key的序列化器
    template.setKeySerializer(new StringRedisSerializer());
    // 设置Value的序列化器为JSON序列化器
    template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    // 设置Hash Key的序列化器
    template.setHashKeySerializer(new StringRedisSerializer());
    // 设置Hash Value的序列化器
    template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

    return template;
    }
    }

    为什么要设置序列化器?

    答:

    RedisTemplate默认使用 JDK 序列化,这会导致存储在 Redis 里的 key 和 value 是二进制的、不可读的格式(类似 \\xac\\xed\\x00\\x05t\\x00\\x03city)。

    通过设置 KeySerializer为 StringRedisSerializer,确保了所有 Redis 的 key 都会以可读的字符串形式存储(例如,直接就是 "city"),这在命令行调试或可视化工具中查看时非常清晰。

    操作几种类型的数据:

    ValueOperations valueOperations = redisTemplate.opsForValue();
    HashOperations hashOperations = redisTemplate.opsForHash();
    ListOperations listOperations = redisTemplate.opsForList();
    SetOperations setOperations = redisTemplate.opsForSet();
    ZSetOperations zSetOperations = redisTemplate.opsForZSet();

    /**
    * 操作字符串类型数据
    */

    @Test
    public void testString() {
    //set
    redisTemplate.opsForValue().set("city", "BEIJING");
    //get
    String city = (String) redisTemplate.opsForValue().get("city");
    System.out.println(city);
    //setex
    redisTemplate.opsForValue().set("province", "shanxi", 2, TimeUnit.MINUTES);
    //setnx
    redisTemplate.opsForValue().setIfAbsent("look", "1");
    redisTemplate.opsForValue().setIfAbsent("look", "2");
    }

    /**
    * 操作哈希类型的数据
    */

    @Test
    public void testHash() {
    //hset het hdel hkeys hvals
    HashOperations hashOperations = redisTemplate.opsForHash();
    hashOperations.put("100", "name", "tom");
    hashOperations.put("100", "age", "20");
    String name = (String) hashOperations.get("100", "age");
    System.out.println(name);

    Set keys = hashOperations.keys("100");
    System.out.println(keys);

    List values = hashOperations.values("100");
    System.out.println(values);
    hashOperations.delete("100", "age");

    }

    /**
    * 操作列表类型数据
    */

    //Lpush Lrange rpop Llen
    @Test
    public void testList() {
    ListOperations listOperations = redisTemplate.opsForList();

    listOperations.leftPushAll("mylist", "a", "b", "c");
    listOperations.leftPush("mylist", "d");

    List mylist = listOperations.range("mylist", 0, 1);
    System.out.println(mylist);

    listOperations.rightPop("mylist");

    Long size = listOperations.size("mylist");
    System.out.println(size);

    }

    @Test
    public void testSet() {
    //sadd smebers scard sinter sunion srem
    SetOperations setOperations = redisTemplate.opsForSet();
    setOperations.add("set1", "a", "b", "c", "d");
    setOperations.add("set2", "b", "c", "d", "e");

    Set members = setOperations.members("set1");
    System.out.println(members);

    Long size = setOperations.size("set1");
    System.out.println(size);

    Set intersection = setOperations.intersect("set1", "set2");
    System.out.println(intersection);

    Set union = setOperations.union("set1", "set2");
    System.out.println(union);

    setOperations.remove("set1", "a", "b");
    }

    /**
    * 操作有序集合
    */

    @Test
    public void testZSet() {
    //zadd zrange zincrby zrem
    ZSetOperations zSetOperations = redisTemplate.opsForZSet();

    zSetOperations.add("zset1", "a", 10);
    zSetOperations.add("zset1", "b", 20);
    zSetOperations.add("zset1", "c", 30);

    Set zset1 = zSetOperations.range("zset1", 0, 1);
    System.out.println(zset1);

    zSetOperations.incrementScore("zset1", "c", 1);

    zSetOperations.remove("zset1", "a");
    }

    /**
    * 通用命令操作
    */

    //keys exists type del
    @Test
    public void testCommon() {

    Set keys = redisTemplate.keys("*");
    System.out.println(keys);

    Boolean name = redisTemplate.hasKey("name");
    Boolean age = redisTemplate.hasKey("set1");

    for (Object key : keys) {
    DataType type = redisTemplate.type(key);
    System.out.println(type.name());
    }
    redisTemplate.delete("mylist");
    }

    HttpClient

    作用:1.发送http请求

    ​2.接受响应数据

    代码解释:

    CloseableHttpClient httpClient = HttpClients.createDefault();

    创建HTTP客户端实例。这是发送请求的基础,类似于打开一个浏览器

    HttpClients.createDefault()会创建一个具有默认配置的HTTP客户端,该客户端支持连接复用(连接池),能有效提升性能

    HttpGet httpGet = new HttpGet(“http://localhost:8080/…”);

    构建GET请求对象。指定了请求的URL(这里是一个本地服务的地址)和HTTP方法(GET)

    HttpGet对象封装了请求的所有信息,包括URL、请求头、参数等。你可以通过 setHeader方法为其添加请求头

    CloseableHttpResponse response = httpClient.execute(httpGet);

    发送请求并获取响应。这是执行网络调用的核心步骤

    httpClient.execute(…)方法会同步地发送请求,并返回一个 CloseableHttpResponse对象,该对象包含了服务器返回的全部信息

    int statusCode = response.getStatusLine().getStatusCode();

    从响应中获取HTTP状态码(如200表示成功,404表示未找到等)

    状态码是HTTP协议规定的一部分,位于响应行的第一行,通过 StatusLine对象获取

    HttpEntity entity = response.getEntity();

    String body = EntityUtils.toString(entity);

    从响应中提取响应体内容。响应体是服务器返回的实际数据(如JSON、HTML等)

    HttpEntity代表了HTTP消息的实体内容(如请求体或响应体)。EntityUtils.toString(…)是一个工具方法,将实体内容流转换为字符串

    response.close();

    httpClient.close();

    关闭连接,释放资源。这非常重要,可以防止资源泄漏

    GET方式请求:

    @Test
    public void testGet() throws IOException {
    //创建httpclient对象
    CloseableHttpClient httpClient = HttpClients.createDefault();
    //创建请求对象
    HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");
    //发送请求并接收响应结果
    CloseableHttpResponse response = httpClient.execute(httpGet);
    //获取服务端返回的状态码
    int statusCode = response.getStatusLine().getStatusCode();
    System.out.println(statusCode);

    HttpEntity entity = response.getEntity();
    String body = EntityUtils.toString(entity);
    System.out.println("服务端返回的数据是"+body);

    //关闭资源
    response.close();
    httpClient.close();
    }

    1. 接收原始数据

    CloseableHttpResponse

    代表完整的HTTP响应,其内部通过 socket 连接接收来自服务器的原始字节流。这是最底层的数据形式。

    优点:完整保留了服务器发送的原始信息。
    缺点:对开发者不友好,无法直接读取和操作。

    2. 协议层封装

    HttpEntity entity = response.getEntity();

    HttpEntity是一个封装对象,它包裹了原始的字节流 (InputStream),并提供了访问响应体(内容字节)、内容类型 (Content-Type) 和内容长度 (Content-Length) 等元数据的方法。它将杂乱的字节流包装成一个结构化的对象。

    优点:提供了一个标准的接口来访问响应内容和元数据,屏蔽了底层字节流的复杂性。
    缺点:内容本身仍是字节,需要进一步处理。

    3. 应用层解码

    String body = EntityUtils.toString(entity);

    这是最关键的一步!EntityUtils.toString()方法会做两件事:

  • 从 HttpEntity中读取字节流。
    2. 根据元数据(如Content-Type头中的charset=UTF-8)或默认设置,将字节流解码成字符串。这一步将机器理解的字节转换成了人类和程序容易处理的文本。
  • post方式请求:

    @Test
    public void testPost() throws IOException, JSONException {
    //创建httpClient对象
    CloseableHttpClient httpClient = HttpClients.createDefault();
    //创建请求对象
    HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");

    JSONObject jsonObject = new JSONObject();
    jsonObject.put("username","admin");
    jsonObject.put("password","123456");

    StringEntity stringEntity = new StringEntity(jsonObject.toString());
    //指定请求编码方式
    stringEntity.setContentEncoding("UTF-8");
    stringEntity.setContentType("application/json");
    httpPost.setEntity(stringEntity);

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

    //解析返回结果
    int statusCode = response.getStatusLine().getStatusCode();
    System.out.println("响应码为:"+statusCode);
    HttpEntity entity = response.getEntity();
    String body = EntityUtils.toString(entity);
    System.out.println("响应数据为"+body);
    //关闭资源
    response.close();
    httpClient.close();
    }

    代码解析:

    CloseableHttpClient httpClient = HttpClients.createDefault();
    创建HTTP客户端实例。这是发送所有请求的基础,类似于打开一个浏览器
    HttpPost httpPost = new HttpPost(“http://localhost…”);
    创建POST请求对象,并设定请求的目标URL(这里是一个本地登录接口)
    JSONObject jsonObject = new JSONObject();
    jsonObject.put(“username”,“admin”);…
    构建JSON格式的请求参数。这里将一个包含用户名(admin)和密码(123456)的Java对象转换为JSON字符串,作为请求体发送
    StringEntity stringEntity = new StringEntity(jsonObject.toString());
    stringEntity.setContentEncoding(“UTF-8”);
    stringEntity.setContentType(“application/json”);
    httpPost.setEntity(stringEntity);
    设置请求体。将JSON字符串包装成StringEntity,并明确指定其内容编码为UTF-8,内容类型为application/json,然后将其放入POST请求中
    CloseableHttpResponse response = httpClient.execute(httpPost);
    执行请求。客户端将携带参数的请求发送至服务器,并获取一个包含所有返回信息的响应对象
    int statusCode = response.getStatusLine().getStatusCode();
    String body = EntityUtils.toString(entity);
    解析响应。从响应中提取HTTP状态码(如200成功)和响应体(服务器返回的实际数据,如登录结果)
    response.close();
    httpClient.close();
    关闭连接,释放资源。这是一个好习惯,可以防止资源泄漏

    (为什么将其包装成StringEnity?

    答:因为HTTP请求体(Body)传输的是字节流(byte stream),而不是Java对象。直接将JSON对象(如JSONObject)放进去是不可能的,必须有一个“转换器”将它变成符合HTTP协议标准的格式。StringEntity就是这个关键的转换器。

    下面这个表格清晰地对比了两种方式的本质区别:

    特性直接放入JSON对象 (不可行)使用StringEntity (标准做法)
    HTTP协议要求 请求体必须是字节流 将字符串转换为字节流,符合协议要求
    数据格式 Java内存中的对象,无法直接传输 字符串(String),是字节流的直接来源
    内容类型(Content-Type) 无法自动设置,服务器无法识别数据格式 可明确设置(如application/json),告诉服务器如何解析
    字符编码 无法控制,可能导致乱码 可指定编码(如UTF-8),确保字符正确传输

    微信登录

    配置微信登陆的设置项:

    38533.png&pos_id=img-eZ15MOEa-1769676124850)

    配置为微信用户生成jwt令牌时使用的配置项:

    在这里插入图片描述

    DTO:
    在这里插入图片描述

    VO:

    在这里插入图片描述

    Controller:

    @RestController
    @RequestMapping("/user/user")
    @Slf4j
    @Api(tags = "C端用户接口")
    public class UserController {
    @Autowired
    private UserService userService;
    @Autowired
    private JwtProperties jwtProperties;
    /**
    * 微信登陆
    * @param userLoginDTO
    * @return
    */

    @PostMapping("/login")
    @ApiOperation("微信登录")
    public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO) {
    log.info("微信用户登录:{}",userLoginDTO.getCode());
    //微信登录
    User user = userService.wxlogin(userLoginDTO);

    //微信用户生成jwt令牌
    Map<String,Object> claims = new HashMap<>();
    claims.put(JwtClaimsConstant.USER_ID,user.getId());
    String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(),jwtProperties.getUserTtl(),claims);

    UserLoginVO userLoginVO = UserLoginVO.builder()
    .id(user.getId())
    .openid(user.getOpenid())
    .token(token)
    .build();

    return Result.success(userLoginVO);
    }
    }

    Service:

    @Service
    @Slf4j
    public class UserServiceImpl implements UserService {

    public static final String WX_LOGIN ="https://api.weixin.qq.com/sns/jscode2session";
    @Autowired
    private WeChatProperties weChatProperties;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserController userController;

    /**
    * 微信登录
    * @param userLoginDTO
    * @return
    */

    @Override
    public User wxlogin(UserLoginDTO userLoginDTO) {
    // Map<String, String> map = new HashMap();
    //
    // map.put("appid",weChatProperties.getAppid());
    // map.put("secret",weChatProperties.getSecret());
    // map.put("js_code",userLoginDTO.getCode());
    // map.put("grant_type","authorization_code");
    // //调用微信接口服务,获取用户的openid
    // String json = HttpClientUtil.doGet(WX_LOGIN,map);
    //
    //
    // JSONObject jsonObject = JSON.parseObject(json);
    // String openid = jsonObject.getString("openid");
    String openid =getOpenid(userLoginDTO.getCode());
    //判断openid是否为空?抛出异常:合法
    if (openid ==null){
    throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
    }
    //判断微信用户是否为外卖系统的新用户,是的话进行注册并保存openid
    User user = userMapper.getByOpenid(openid);
    if (user == null){
    user = User.builder()
    .openid(openid)
    .createTime(LocalDateTime.now())
    .build();
    userMapper.insert(user);
    }
    return user;
    }
    private String getOpenid(String code) {
    Map<String, String> map = new HashMap();

    map.put("appid",weChatProperties.getAppid());
    map.put("secret",weChatProperties.getSecret());
    map.put("js_code",code);
    map.put("grant_type","authorization_code");
    //调用微信接口服务,获取用户的openid
    String json = HttpClientUtil.doGet(WX_LOGIN,map);

    JSONObject jsonObject = JSON.parseObject(json);
    String openid = jsonObject.getString("openid");
    return openid;
    }
    }

    还有jwt的拦截器编写和注册拦截器…(和jwt登录验证一起统一学习)

    缓存菜品

    在这里插入图片描述

    例如:在查询菜品的时候开始是否缓存?

    /**
    * 根据分类id查询菜品
    *
    * @param categoryId * @return
    */

    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result<List<DishVO>> list(Long categoryId) {

    //构造redis中的key,规则:dish_分类id
    String key = "dish_"+categoryId;

    //查询redis中是否存在菜品数据
    List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
    if (list != null && list.size()>0) {
    //如果存在,直接返回,无需查询数据库
    return Result.success(list);
    }

    Dish dish = new Dish();
    dish.setCategoryId(categoryId);
    dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
    //如果不存在,查询数据库,将查询到的数据放入redis中
    list = dishService.listWithFlavor(dish);
    redisTemplate.opsForValue().set(key,list);
    return Result.success(list);
    }

    删除缓存操作(像在进行对菜品修改的时候,对菜品删除的时候,它的缓存数据就需要删除):

    private void claeanCache(String pattern){
    Set keys = redisTemplate.keys(pattern);
    redisTemplate.delete(keys);
    }

    利用注解来进行缓存:

    在这里插入图片描述

    @Cacheable

    • 作用:适用于查询方法。遵循“先查缓存,没有再执行方法”的流程。
    • 用法:标注在方法上。主要指定两个属性: value/ cacheNames:缓存名称(必填),可指定多个,如 @Cacheable("users")或 @Cacheable({"users", "admins"})。 key:缓存键(可选)。支持 SpEL 表达式,默认为方法参数组合。如 @Cacheable(value="user", key="#id")。 condition:条件缓存(可选)。满足条件才缓存,如 @Cacheable(…, condition="#id > 10")。

    @CachePut

    • 作用:适用于更新/新增方法。总是会执行方法,并将方法返回的结果更新到缓存中。
    • 用法:通常用于更新数据库后同步更新缓存。必须和 @Cacheable使用相同的 value和 key,以确保更新的是同一份数据。
    • 注意:与 @Cacheable最大区别在于,它从不跳过方法执行。

    @CacheEvict

    • 作用:适用于删除方法。用于从缓存中移除数据。
    • 用法:标注在方法上。关键属性: value/ cacheNames:指定要清除的缓存名称。 key:指定要清除的缓存键。 allEntries:是否清空整个缓存区域(可选,默认为false)。若为 true,则忽略 key,清除 value下的所有缓存。 beforeInvocation:清除操作在方法调用前还是调用后执行(可选,默认为false即方法调用后执行)。设置为 true可避免方法执行异常导致缓存未清除。

    微信支付:

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    Spring Task定时任务编写:

    @Scheduled(cron = "0 0 1 * * ?")//每天凌晨一点触发一次

    /**
    * 处理一直处于派送中的订单
    */
    @Scheduled(cron = "0 0 1 * * ?")//每天凌晨一点触发一次
    public void processDeliveryOrder(){
    log.info("定时处理处于派送中的订单:{}", LocalDateTime.now());
    List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS,LocalDateTime.now().plusMinutes(-60));
    for (Orders orders : ordersList) {
    orders.setStatus(Orders.COMPLETED);
    orderMapper.update(orders);
    }
    }
    }

    利用此注解进行定时任务触发。

    Websocket:

    在这里插入图片描述

    实现步骤:

    直接使用websocket.html页面作为WebSocket客户端
    导入WebSocket的maven坐标
    导入WebSocket服务端组件WebSocketServer,用于和客户端通信
    导入配置类WebSocketConfiguration,注册WebSocket的服务端组件
    导入定时任务类WebSocketTask,定时向客户端推送数据

    WebSocketServer:

    /**
    * WebSocket服务
    */
    @Component
    @ServerEndpoint("/ws/{sid}")
    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();
    }
    }
    }

    }

    APch@ServerEndpoint(“/ws/{sid}”)
    将该类声明为一个WebSocket服务端点,客户端通过 ws://地址:端口/ws/客户端唯一标识连接
    @Component
    表明这是一个Spring管理的Bean,通常需要配合ServerEndpointExporter配置才能生效
    sessionMap
    一个静态的Map,核心设计,用于全局保存所有已连接的客户端会话(Session)及其标识(sid)
    @OnOpen
    标注客户端连接成功时调用的方法,用于初始化操作
    @OnMessage
    标注收到客户端消息时调用的方法,用于处理业务逻辑
    @OnClose
    标注连接关闭时调用的方法,用于清理资源
    @PathParam(“sid”)
    用于从连接路径中提取变量sid的值,作为客户端的唯一标识
    sendToAllClient
    一个自定义的群发方法,用于向所有连接的客户端推送消息

    代码片段

    角色与作用

    通俗理解

    session

    代表一次独立的 WebSocket 连接

    好比是和一个朋友建立的专属电话线路

    .getBasicRemote()

    获取一个同步的消息发送对象

    选择用普通电话模式交流(说完一句等对方回应)

    .sendText(message)

    同步地发送文本消息

    在电话里说出一句话,并等待对方听到

    WebSocketConfiguration

    /**
    * WebSocket配置类,用于注册WebSocket的Bean
    */

    @Configuration
    public class WebSocketConfiguration {

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

    }

    测试任务:

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

    Apache ECharts

    /**
    * 订单统计
    * @param begin
    * @param end
    * @return
    */

    @GetMapping("/ordersStatistics")
    @ApiOperation("订单数据统计")
    public Result<OrderReportVO> ordersStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd")LocalDate begin,
    @DateTimeFormat(pattern = "yyyy-MM-dd")LocalDate end)
    {
    log.info("订单数据统计:{},{}",begin,end);
    return Result.success(reportService.getOrderStatistics(begin,end));
    }

    @Override
    public OrderReportVO getOrderStatistics(LocalDate begin, LocalDate end) {
    List<LocalDate> dateList = new ArrayList<>();
    dateList.add(begin);
    while(!begin.equals(end)){
    begin = begin.plusDays(1);
    dateList.add(begin);
    }
    List<Integer> orderCountList = new ArrayList<>();
    List<Integer> ValidOrderCountList = new ArrayList<>();
    Integer TotalOrderCount = 0;
    Integer ValidOrderCount = 0;
    for (LocalDate date : dateList) {
    LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
    LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
    Map map = new HashMap();
    map.put("begin", beginTime);
    map.put("end", endTime);
    map.put("status", Orders.COMPLETED);
    Integer totalOrder = userMapper.countOrderByMap(map);
    TotalOrderCount = TotalOrderCount + totalOrder;
    orderCountList.add(totalOrder);
    Integer validOrder = userMapper.countOrderValidByMap(map);
    ValidOrderCount = ValidOrderCount + validOrder;
    ValidOrderCountList.add(validOrder);
    }
    Double OrderCompletionRate = ValidOrderCount.doubleValue()/TotalOrderCount.doubleValue();
    return OrderReportVO.builder()
    .dateList(StringUtils.join(dateList, ","))
    .orderCountList(StringUtils.join(orderCountList, ","))
    .validOrderCountList(StringUtils.join(ValidOrderCountList, ","))
    .totalOrderCount(TotalOrderCount)
    .validOrderCount(ValidOrderCount)
    .orderCompletionRate(OrderCompletionRate)
    .build();
    }

    <select id="countOrderByMap" resultType="java.lang.Integer">
    select count(id) from orders
    <where>
    <if test="begin != null">
    and order_time &gt;#{begin}
    </if>
    <if test="end != null">
    and order_time &lt;#{end}
    </if>
    </where>
    </select>

    Apache POI:

    @Override
    public void exportBusinessData(HttpServletResponse response) {
    //1.查询数据库,获取营业数据————查询销量30天的运营数据
    LocalDate dateBegin = LocalDate.now().minusDays(30);
    LocalDate dateEnd = LocalDate.now().minusDays(1);
    LocalDateTime beginTime = LocalDateTime.of(dateBegin, LocalTime.MIN);
    LocalDateTime endTime = LocalDateTime.of(dateEnd, LocalTime.MAX);
    BusinessDataVO businessDataVO = workspaceService.getBusinessData(beginTime, endTime);
    //2.通过poi将数据写入excel文件
    InputStream in = this.getClass().getClassLoader().getResourceAsStream("template/运营数据报表模板.xlsx");
    try {
    //基于模板文件创建一个心的excel文件
    XSSFWorkbook excel = new XSSFWorkbook(in);

    XSSFSheet sheet = excel.getSheet("Sheet1");
    //填充数据–时间
    sheet.getRow(1).getCell(1).setCellValue("时间:"+dateBegin+"至:"+dateEnd);
    sheet.getRow(3).getCell(2).setCellValue(businessDataVO.getTurnover());
    sheet.getRow(3).getCell(4).setCellValue(businessDataVO.getOrderCompletionRate());
    sheet.getRow(3).getCell(6).setCellValue(businessDataVO.getNewUsers());
    //获得第五行
    XSSFRow row = sheet.getRow(4);
    row.getCell(2).setCellValue(businessDataVO.getValidOrderCount());
    row.getCell(4).setCellValue(businessDataVO.getUnitPrice());

    //填充明细数据
    for (int i = 0; i < 30; i++) {
    LocalDate date = dateBegin.plusDays(i);
    //查询某一天的营业数据
    BusinessDataVO businessData = workspaceService.getBusinessData(LocalDateTime.of(date, LocalTime.MIN), LocalDateTime.of(date, LocalTime.MAX));
    //获得行
    row = sheet.getRow(7 + i);
    //获得单元格
    row.getCell(1).setCellValue(date.toString());
    row.getCell(2).setCellValue(businessData.getTurnover());
    row.getCell(3).setCellValue(businessData.getValidOrderCount());
    row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
    row.getCell(5).setCellValue(businessData.getUnitPrice());
    row.getCell(6).setCellValue(businessData.getNewUsers());

    }
    //3.通过输出流将excel文件下载到客户端浏览器
    ServletOutputStream out = response.getOutputStream();
    excel.write(out);
    //关闭资源
    out.close();
    excel.close();
    } catch (IOException e) {
    throw new RuntimeException(e);
    }

    }

    赞(0)
    未经允许不得转载:171主机测评 » 苍穹外卖复习
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址