4.java秒杀项目第四课-后端商品后台接口

使用逆向工程对数据表t_goods进行业务代码生成

java 复制代码
package com.yxl.generator;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.sql.Types;
import java.util.Collections;

public class CodeGenerator
{
    //    // 添加辅助方法
    //    private static int getJdbcTypeCode(org.apache.ibatis.type.JdbcType jdbcType) {
    //        try {
    //            return Types.class.getField(jdbcType.name()).getInt(null);
    //        } catch (Exception e) {
    //            return Integer.MIN_VALUE;
    //        }
    //    }
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://rm-hp3w7yctdnkg66ow31o.mysql.huhehaote.rds.aliyuncs.com:3306/seckill?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&useSSL=false", "testuser", "btlf123456*")
        .globalConfig(builder -> {
            builder.author("yxl") // 设置作者
            .enableSwagger() // 开启 swagger 模式
            .outputDir("G://java"); // 指定输出目录
        })
        .dataSourceConfig(builder ->
                          builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
                              int typeCode = metaInfo.getJdbcType().TYPE_CODE;
                              if (typeCode == Types.SMALLINT) {
                                  // 自定义类型转换
                                  return DbColumnType.INTEGER;
                              }
                              return typeRegistry.getColumnType(metaInfo);
                          })
                         )
        .packageConfig(builder ->
                       builder.parent("com.yxl.seckill") // 设置父包名
                       .moduleName("") // 设置父包模块名
                       .pathInfo(Collections.singletonMap(OutputFile.xml, "G://java//com//yxl//seckill")) // 设置mapperXml生成路径
                      )
        .strategyConfig(builder ->
                        builder.addInclude("t_goods") // 设置需要生成的表名
                        //                    .addTablePrefix("t_", "c_") // 设置过滤表前缀
                       )
        .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
        .execute();
    }
}

编写IGoodService中的方法

java 复制代码
package com.yxl.seckill.service;

import com.yxl.seckill.entity.TGoods;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yxl.seckill.vo.GoodsVo;

import java.util.List;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author yxl
 * @since 2025-10-06
 */
public interface ITGoodsService extends IService<TGoods> {
    List<GoodsVo> findGoodsVo();
    GoodsVo findGoodsVoByGoodsId(Long goodsId);
}
  • 在TGoodsMapper编写业务接口
    • 使用注解'@Select'完成查询

在TGoodsMapper文件下对findGoodsVo方法,通过'@Select'注解实现

java 复制代码
import java.util.List;

import org.apache.ibatis.annotations.Select;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author yxl
 * @since 2025-12-07
 * @version 1.0
 */
public interface TGoodsMapper extends BaseMapper<TGoods> {
 @Select("select g.*,mg.stock_count,mg.start_date,mg.end_date,mg.seckill_price from t_goods g left join t_seckill_goods mg on g.id=mg.goods_id")
    List<GoodsVo> findGoodsVo();
    GoodsVo findGoodsVoByGoodsId(Long goodsId);
}

GoodsVo 实体类

plain 复制代码
import com.yxl.seckill.entity.TGoods;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class GoodsVo extends TGoods
{
    private BigDecimal seckillPrice;
    private Integer stockCount;
    private Date startDate;
    private Date endDate;
}
复制代码
- 在Mapper.xml中完成查询
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yxl.seckill.mapper.TGoodsMapper">
  <!-- 根据商品id获取商品详情 -->
  <select id="findGoodsVoByGoodsId" resultType="com.yxl.seckill.vo.GoodsVo">
    SELECT
    g.id,
    g.goods_name,
    g.goods_title,
    g.goods_img,
    g.goods_detail,
    g.goods_price,
    g.goods_stock,
    sg.seckill_price,
    sg.stock_count,
    sg.start_date,
    sg.end_date
    FROM
    t_goods AS g
    LEFT JOIN t_seckill_goods AS sg ON g.id = sg.goods_id
    WHERE g.id = #{goodsId}
  </select>
</mapper>

:::color1

TGoodsMapper.javaTGoodsMapper.xml这两个文件是通过 MyBatis 框架的机制关联起来的,具体关联方式如下:

命名空间关联(Namespace): 在 TGoodsMapper.xml 文件中,根元素 的 namespace 属性设置为 com.yxl.seckill.mapper.TGoodsMapper,这正好对应于 TGoodsMapper.java 接口的全限定名。

方法与 SQL 语句关联:

XML 文件中的 元素的 id 属性值对应 Mapper 接口中方法的名称

在这个例子中,XML 文件中的 findGoodsVoByGoodsId 查询语句对应了 TGoodsMapper.java 接口中的 findGoodsVoByGoodsId 方法

混合使用注解和 XML 映射:

TGoodsMapper 接口展示了 MyBatis 的一个特性:可以同时使用注解和 XML 映射文件

findGoodsVo 方法使用了 @Select 注解直接定义 SQL 查询

而 findGoodsVoByGoodsId 方法没有注解,它的实现定义在 XML 文件中

这种设计允许开发者根据具体情况选择使用注解还是 XML 映射:

简单的 SQL 查询通常使用注解方式(如 findGoodsVo 方法)

复杂的 SQL 查询或者需要动态 SQL 的情况更适合使用 XML 映射文件(如 findGoodsVoByGoodsId 方法)

当 MyBatis 启动时,它会扫描这些 Mapper 接口,并将接口方法与对应的 SQL 实现(无论是注解还是 XML)进行绑定,形成完整的映射关系。

:::

在TGoodsServiceImpl.java中进行方法实现
在TGoodsController中进行toList方法实现,为前端提供接口需要验证用户登录状态
java 复制代码
    /**
     * 跳转登录页
     *
     * @return
     */
    @RequestMapping(value="/toList")
    @ResponseBody
    public String toList(HttpSession session, @CookieValue(value = "userTicket",required = false) String ticket) {


        if (!StringUtils.hasText(ticket)) {
            return "login1";
        }
        TUser user = (TUser) session.getAttribute(ticket);
        if (null == user) {
            return "login2";
        }
        return "goodsList";
    }

由于通过HttpSession session,需要每个校验接口都需要传参,为简化编写方式我们进行参数优化

WebMvcConfigurer中addArgumentResolvers方法的使用

用户第一次登录之后会得到一个cookie,在以后每次的访问过程中都会携带Cookie进行访问。在后台的Controller中对于需要登录权限的访问接口都要先获取Cookie中的Token,再使用Token从session中获取用户登录信息来判断用户登录情况决定是否放行。

如果在每个需要登录权限访问的方法中都要写这个逻辑就会使代码重复,出现了冗余。代码基本如下:

java 复制代码
@GetMapping("goods")
public Result showGoods1(HttpSession session, @CookieValue("Token") String token){
    // 你每次都要在这里判断token或session
    if (token == null || MyObjectUtil.isEmpty(session.getAttribute(token))){
        return Result.error();
    }
    return Result.ok();
}

所以需要寻找是否有更好的解决方法。

最终找到<font style="color:rgb(192, 52, 29);background-color:rgba(0, 0, 0, 0.04);">addArgumentResolvers()</font>这个方法。只需要在这个方法中进行处理即可。

代码如下:

  • Controller中的方法
java 复制代码
    @GetMapping("goods")
    public CommonResult showGoods(TUser user){
        // 注意:这里的User参数不是由前端传入的,而是由addArgumentResolvers方法处理之后传进来的
       
        // 根据处理之后传入的参数判断是否登录
        if (user == null)
            return CommonResult.error();
        return CommonResult.success();
    }
  • 在config包下创建 WebConfig实现了类
java 复制代码
package com.yxl.seckill.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer
{
    @Autowired
    private UserArgumentResolver userArgumentResolver;
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(userArgumentResolver);
    }
}
  • 在config包下创建 UserArgumentResolver自定义实现类
java 复制代码
package com.yxl.seckill.config;

import com.yxl.seckill.entity.TUser;
import com.yxl.seckill.service.ITUserService;
import com.yxl.seckill.util.CookieUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
@Component
public class UserArgumentResolver implements HandlerMethodArgumentResolver
{
    @Autowired
    private ITUserService userService;
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        Class<?> clazz = parameter.getParameterType();
        return clazz == TUser.class;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request =
            webRequest.getNativeRequest(HttpServletRequest.class);
        HttpServletResponse response =
            webRequest.getNativeResponse(HttpServletResponse.class);
        String ticket = CookieUtil.getCookieValue(request, "userTicket");
        if (StringUtils.isEmpty(ticket)) {
            return null;
        }
        TUser user = userService.getByUserTicket(ticket, request, response);
        return user;
    }
}
json 复制代码
package com.yxl.seckill.service;

import com.yxl.seckill.entity.TUser;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yxl.seckill.vo.CommonResult;
import com.yxl.seckill.vo.LoginVo;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author yxl
 * @since 2025-09-23
 */
public interface ITUserService extends IService<TUser> {
    CommonResult<String> login(HttpServletRequest request, HttpServletResponse response, LoginVo loginVo);
    TUser getByUserTicket(String userTicket, HttpServletRequest request,
                          HttpServletResponse response);
}

getByUserTicket 具体实现

java 复制代码
  @Override
    public TUser getByUserTicket(String userTicket, HttpServletRequest request, HttpServletResponse response)
    {
        if (!StringUtils.hasText(userTicket)) {
            return null;
        }
        String userJson = (String) redisTemplate.opsForValue().get("user:" +
            userTicket);
        if (!StringUtils.hasText(userJson)) {
            return null;
        }
        TUser user = JsonUtil.jsonStr2Object(userJson, TUser.class);
        if (null != user) {
            CookieUtil.setCookie(request,response,"userTicket",userTicket);
        }
        return user;

    }
}

改造前端页面

后端商品后台接口,展示商品信息。代码片段如下

vue 复制代码
  // 从接口获取商品数据
  const fetchProducts = async () => {
    try {
      loading.value = true
      // 使用代理路径,避免跨域问题
      const response = await api.get('/tGoods/toList')
      console.log('接口返回数据:', response)
      console.log('原始goodsVo数据:', response?.goodsVo)

      // 检查接口返回数据格式
      if (response && response.goodsVo) {
        try {
          // 解析JSON字符串为JavaScript对象
          const goodsVoArray = JSON.parse(response.goodsVo)
          console.log('解析后的商品数据:', goodsVoArray)

          // 将接口数据转换为页面需要的格式
          products.value = goodsVoArray.map(item => ({
            ...item,
            // 确保图片链接格式正确
            goodsImg: item.goodsImg.replace(/`/g, '').trim(),
            // 兼容接口字段名和页面使用的字段名
            stock: item.stockCount, // 保持与原代码一致的字段名
            initialStock: item.goodsStock // 保持与原代码一致的字段名
          }))
        } catch (parseError) {
          console.error('解析商品数据失败:', parseError)
          ElMessage.error('解析商品数据失败')
        }
      } else {
        ElMessage.error('获取商品数据失败: 数据格式错误')
      }
    } catch (error) {
      console.error('获取商品数据失败:', error)
      ElMessage.error('获取商品数据失败,请稍后重试')

      // 在接口无法访问时,使用模拟数据作为备用
      // products.value = [
      //   {
      //     id: 1,
      //     goodsName: "IPHONE14",
      //     goodsTitle: "ITPHNE12 64G",
      //     goodsImg: "https://img14.360buyimg.com/n0/jfs/t1/210902/35/25956/18752/631921e2Ec9898853/e72dc32de89a32d4.jpg",
      //     goodsDetail: "IPHONE 14 64GB ",
      //     goodsPrice: 1620.0,
      //     goodsStock: 10,
      //     seckillPrice: 629.0,
      //     stockCount: 7,
      //     stock: 7, // 兼容原代码
      //     initialStock: 10, // 兼容原代码
      //     startDate: new Date().getTime() - 60000, // 1分钟前开始
      //     endDate: new Date().getTime() + 3600000 // 1小时后结束
      //   },
      //   {
      //     id: 2,
      //     goodsName: "IPHONE14 PRO",
      //     goodsTitle: "IPHONE14 PRO 128GB",
      //     goodsImg: "https://img14.360buyimg.com/n0/jfs/t1/220816/15/21180/46541/63191de8Ecc2cc786/54d338b11ed10615.jpg",
      //     goodsDetail: "IPHONE14 PRO 128GB",
      //     goodsPrice: 9299.0,
      //     goodsStock: 100,
      //     seckillPrice: 800.0,
      //     stockCount: 100,
      //     stock: 100, // 兼容原代码
      //     initialStock: 100, // 兼容原代码
      //     startDate: new Date().getTime() - 300000, // 5分钟前开始
      //     endDate: new Date().getTime() + 1800000 // 30分钟后结束
      //   }
      // ]
    } finally {
      loading.value = false
    }
  }

优化后端商品后台接口

将商品信息进行缓存,何时缓存、何时读取缓存数据

\[4.1 JMeter 压测工具使用\]

相关推荐
唠玖馆6 分钟前
c++AVL树
开发语言·c++
2401_858286118 分钟前
130.【C语言】可变参数宏
c语言·开发语言
霸道流氓气质16 分钟前
通义千问 VL & Audio:图像、视频、语音多模态 Java 集成深度解析
java·开发语言·音视频
jayson.h19 分钟前
python-可视化提取表格-通用
开发语言·python
lemon_sjdk20 分钟前
WCPoint、WebPageClient和Accessor类源码
java·安全·webkit·javafx
vx-Biye_Design21 分钟前
springboot中国传统节日宣传平台49078-计算机课程设计、毕业设计
java·前端·vue.js·spring boot·后端·课程设计·idea
野生技术架构师32 分钟前
从向量检索到混合搜索优化:Spring AI 2.0.1 + pgvector RAG 实战
java·人工智能·spring
零依赖极客37 分钟前
《30 天手搓 ARM 架构零依赖纯 C 推理引擎》课程介绍与大纲
c语言·开发语言·arm开发·人工智能·嵌入式硬件·ai编程
qq_4523962339 分钟前
第三篇:《变量、类型与函数:Rust 的“基本盘”》
开发语言·后端·rust
5335ld40 分钟前
app版本更新(vue3+unibest+静默更新+强制更新)
开发语言·javascript·ecmascript