当前位置: 首页 > news >正文

SpringBoot+Vue+Mysql苍穹外卖

一.项目介绍

1.项目内容

苍穹外卖是一款为大学学子设计的校园外卖服务软件,旨在提供便捷的食堂外卖送至宿舍的服务。该软件包含系统管理后台和用户端(微信小程序)两部分,支持在线浏览菜品、添加购物车、下单等功能,并由学生兼职提供跑腿送餐服务。

2.技术栈

SpringBoot+Vue+Mybatis+Mysql+Redis+Nginx

3.Nginx

网页-->nginx-->服务器

nginx反向代理优势:

1.提高访问速度(nginx可以做缓存)

2.进行负载均衡(将大量请求均匀分发请求)

3.保证后端服务的安全

        # 反向代理,处理管理端发送的请求location /api/ {proxy_pass   http://localhost:8080/admin/;#proxy_pass   http://webservers/admin/;}# 反向代理,处理用户端发送的请求location /user/ {proxy_pass   http://webservers/user/;}

4.Swagger

  @Beanpublic Docket docket() {log.info("准备生成接口文档");ApiInfo apiInfo = new ApiInfoBuilder().title("苍穹外卖项目接口文档").version("2.0").description("苍穹外卖项目接口文档").build();Docket docket = new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo).select().apis(RequestHandlerSelectors.basePackage("com.sky.controller")).paths(PathSelectors.any()).build();return docket;}

常用注解

5.HttpClient

1.概述:用来提供高效的,丰富的http协议的用户端编程工具包

2.核心api

  • HttpClient
  • HttpClients
  • CloseableHttpClient
  • HttpGet
  • HttpPost

3.简单使用

发送请求步骤
1.创建HttpClient对象
2.创建Http请求对象
3.调用HttpClient的execute方法发送请求发送Get请求
@Testpublic void testGET() throws IOException {//创建httpclient对象CloseableHttpClient httpClient = HttpClients.createDefault();//创建请求对象HttpGet getstatus = new HttpGet("http://localhost:8080/user/shop/status");//发送请求CloseableHttpResponse response = httpClient.execute(getstatus);//获取服务端返回的状态码int statusCode = response.getStatusLine().getStatusCode();System.out.println(statusCode);HttpEntity entity = response.getEntity();if (entity != null) {String body = EntityUtils.toString(entity);System.out.println(body);}//关闭资源response.close();httpClient.close();
}发送POST请求
@Test
public void testPOST() throws IOException {//创建httpclient对象CloseableHttpClient httpClient = HttpClients.createDefault();//创建请求对象HttpPost post = new HttpPost("http://localhost:8080/admin/employee/login");//构造请求的数据JSONObject jsonObject = new JSONObject();jsonObject.put("username", "admin");jsonObject.put("password", "123456");StringEntity entity = new StringEntity(jsonObject.toString());entity.setContentEncoding("UTF-8");entity.setContentType("application/json");post.setEntity(entity);//发送请求CloseableHttpResponse response = httpClient.execute(post);//获取服务端返回的状态码int statuscode = response.getStatusLine().getStatusCode();System.out.println(statuscode);//具体的响应数据HttpEntity entity1 = response.getEntity();if (entity1 != null) {String body = EntityUtils.toString(entity1);System.out.println(body);}//关闭资源response.close();httpClient.close();
}

6.微信小程序

概述:
  • 开发者可以快速地开发一个小程序。小程序可以在微信内被便捷地获取和传播,同时具有出色的使用体验。
  • 微信开发平台:微信开放平台
接入流程
  • 注册
  • 小程序信息完善
  • 开发小程序
  • 提交审核和发布
注册并且完善信息

微信开发者工具开发小程序

二.具体实现

一.登录功能

用户注册,输入密码-->对密码进行md5加密进行存储-->用户进行登录,密文解码进行比对

    @PostMapping("/login")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);}public Employee login(EmployeeLoginDTO employeeLoginDTO) {String username = employeeLoginDTO.getUsername();String password = employeeLoginDTO.getPassword();//1、根据用户名查询数据库中的数据Employee employee = employeeMapper.getByUsername(username);//2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)if (employee == null) {//账号不存在throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);}//密码比对,先进行md5加密再进行密码比较password = DigestUtils.md5DigestAsHex(password.getBytes());if (!password.equals(employee.getPassword())) {//密码错误throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);}if (employee.getStatus() == StatusConstant.DISABLE) {//账号被锁定throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);}//3、返回实体对象return employee;}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断当前拦截到的是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;}}

二.公共字段自动填充

自定义注解AutoFill,用于标识需要公共字段自定义填充的方法
自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值
在Mapper上加入AutoFill注解public enum OperationType {UPDATE,INSERT
}@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {OperationType value();
}@Aspect
@Component
@Slf4j
public class AutoFillAspect {/*** 切入点*/@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")public void AutoFillCut() {}/*** 前置通知,为公共字段进行赋值*/@Before("AutoFillCut()")public void AutoFill(JoinPoint joinPoint) throws Exception {log.info("AutoFill start");//获取当前数据库操作的类型MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();AutoFill autoFill = methodSignature.getMethod().getAnnotation(AutoFill.class);OperationType operationType = autoFill.value();//获取当前被拦截方法的操作实体Object[] args = joinPoint.getArgs();if(args == null || args.length == 0) {return;}Object entity=args[0];//准备赋值的数据LocalDateTime now= LocalDateTime.now();Long currentId = BaseContext.getCurrentId();//为实体进行赋值if (operationType == OperationType.INSERT) {Method setCrateTime= entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setCrateUser=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);Method setUpdateTime=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);setCrateTime.invoke(entity,now);setCrateUser.invoke(entity,currentId);setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} else if (operationType == OperationType.UPDATE) {Method setUpdateTime=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);}}
}

三.员工管理

新增员工
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO) {log.info("新增员工{}",employeeDTO);employeeService.save(employeeDTO);return Result.success();
}void save(EmployeeDTO employeeDTO);
public void save(EmployeeDTO employeeDTO) {Employee employee  = new Employee();//对象属性拷贝BeanUtils.copyProperties(employeeDTO,employee);employee.setStatus(StatusConstant.ENABLE);employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());//设置当前记录人的id//TODO 后期改为当前用户的idemployee.setCreateUser(10L);employee.setUpdateUser(10L);employeeMapper.insert(employee);}@Insert("INSERT INTO employee(name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user, status) " +"VALUES (#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}, #{status})")void insert(Employee employee);@ExceptionHandlerpublic Result exceptionHandler(SQLIntegrityConstraintViolationException ex){String message =  ex.getMessage();if (message.contains("Duplicate entry")){String[] split = message.split(" ");String username = split[2];String msg = username + MessageConstant.ALREADY_EXIST;return Result.error(msg);}else{return Result.error(MessageConstant.UNKNOWN_ERROR);}}ThreadLocal
它为每个线程提供了一个独立的变量副本,使得每个线程可以独立地访问和修改自己的变量副本
一个请求一个线程
public class BaseContext {public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();public static void setCurrentId(Long id) {threadLocal.set(id);}public static Long getCurrentId() {return threadLocal.get();}public static void removeCurrentId() {threadLocal.remove();}
}
拦截器
BaseContext.setCurrentId(empId);
//新增员工时设置当前记录人的id
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());
查询员工
PageHelper 是一个基于 MyBatis 的分页插件,用于简化分页查询的实现。
它通过 MyBatis 的拦截器机制,自动在 SQL 查询中添加分页逻辑.@GetMapping("/page")@ApiOperation("员工分页查询")public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO) {log.info("查询员工{}",employeePageQueryDTO);PageResult pageResult = employeeService.page(employeePageQueryDTO);return Result.success(pageResult);}PageResult page(EmployeePageQueryDTO employeePageQueryDTO);
@Override
public PageResult page(EmployeePageQueryDTO employeePageQueryDTO) {//开始分页查询PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);long total = page.getTotal();List<Employee> records = page.getResult();return new PageResult(total,records);}Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
<select id="pageQuery" resultType="com.sky.entity.Employee">select * from employee<where><if test="name != null and name != ''">and name like concat('%',#{name},'%')</if></where>order by create_time desc
</select>
编辑员工
@PutMapping@ApiOperation("修改员工信息")public Result update(@RequestBody EmployeeDTO employeedao) {employeeService.update(employeedao);return Result.success();}void update(EmployeeDTO employee);
@Override
public void update(EmployeeDTO employeedao) {Employee employee = new Employee();BeanUtils.copyProperties(employeedao,employee);employee.setUpdateTime(LocalDateTime.now());employee.setUpdateUser(BaseContext.getCurrentId());employeeMapper.update(employee);
}<update id="update" parameterType="Employee">update employee<set><if test="name!=null">name = #{name},</if><if test="username!=null">username = #{username},</if><if test="password!=null">password = #{password},</if><if test="phone!=null">phone = #{phone},</if><if test="sex!=null">sex = #{sex},</if><if test="idNumber!=null">id_number = #{idNumber},</if><if test="updateTime!=null">update_time = #{updateTime},</if><if test="updateUser!=null">update_user = #{updateUser},</if><if test="status!=null">status = #{status}, </if></set>where id = #{id}</update>

四.菜品管理

新增菜品
@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishdao) {log.info("新增菜品{}",dishdao);dishService.saveWithFlavor(dishdao);return Result.success();
}public void saveWithFlavor(DishDTO dishdao);
@PostMapping
@Override
@Transactional
public void saveWithFlavor(DishDTO dishdao) {//向菜品表插入1条数据Dish dish = new Dish();BeanUtils.copyProperties(dishdao, dish);dishMapper.insert(dish);//向口味表插入n条数据//获取insert语句的主键值long  dish_id = dish.getId();List<DishFlavor> flavors=dishdao.getFlavors();if (flavors!=null&&flavors.size()>0){flavors.forEach(dishFlavor -> dishFlavor.setDishId(dish_id));dishFloarMapper.insertBatch(flavors);}
}@AutoFill(OperationType.INSERT)
void insert(Dish dish);
<insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into dish(name,category_id,price,image,description,status,create_time,update_time,create_user,update_user)values(#{name},#{categoryId},#{price},#{image},#{description},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})
</insert>void insertBatch(List<DishFlavor> flavors);
<insert id="insertBatch">insert into dish_flavor(dish_id, name, value)values<foreach collection="flavors" item="df" separator=",">(#{df.dishId}, #{df.name}, JSON_ARRAY(#{df.value}))</foreach>
</insert>
查询菜品
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> GetDish(DishPageQueryDTO dto){log.info("菜品查询");PageResult list =dishService.getDish(dto);return Result.success(list);
}PageResult getDish(DishPageQueryDTO dto);
public PageResult getDish(DishPageQueryDTO dto) {PageHelper.startPage(dto.getPage(),dto.getPageSize());Page<DishVO> page = dishMapper.pageQuery(dto);return new PageResult(page.getTotal(),page.getResult());
}Page<DishVO> pageQuery(DishPageQueryDTO dto);
<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 = #{category_id}</if><if test="status!=null">and d.status = #{status}</if></where>order by d.create_time desc
</select>
删除菜品
@DeleteMapping()
@ApiOperation("删除菜品")public Result delete(@RequestParam List<Long> ids) {log.info("删除菜品{}",ids);dishService.delete(ids);return Result.success();
}@Transactional
@Override
public void delete(List<Long> ids) {//起售中的菜品不能删除for (Long id : ids) {Dish dish = dishMapper.geibyid(id);if (dish.getStatus() == StatusConstant.ENABLE){throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);}}//被套餐关联的菜品不能删除List<Long> SetmealIds = setmealDishMapper.getSetmealDishIdsBydishlId(ids);if (SetmealIds!=null&&SetmealIds.size()>0){throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);}/*可以删除一个菜品,也可以删除多个菜品for (Long id : ids) {dishMapper.delete(id);//删除菜品后,关联的口味也需要删除dishFloarMapper.delete(id);}*///优化,根据菜品id集合批量删除dishMapper.deletes(ids);dishFloarMapper.deletes(ids);
}void deletes(List<Long> ids);
<delete id="deletes">delete from dish where id in<foreach collection="ids" open="(" close=")" separator="," item="id">#{id}</foreach></delete>
修改菜品
@PutMapping@ApiOperation("修改菜品")public Result update(@RequestBody DishDTO dishdao) {dishService.update(dishdao);return Result.success();
}void update(DishDTO dishdao);
public void update(DishDTO dishdao) {//修改菜品表Dish dish = new Dish();BeanUtils.copyProperties(dishdao, dish);dishMapper.updatedish(dish);//修改口味表,先删除所有口味,在插入传过来的口味dishFloarMapper.delete(dishdao  .getId());List<DishFlavor> flavors=dishdao.getFlavors();if (flavors!=null&&flavors.size()>0){flavors.forEach(dishFlavor -> {dishFlavor.setDishId(dish.getId());});flavors.forEach(dishFlavor -> dishFlavor.setValue(dishFlavor.getValue().toString()));dishFloarMapper.insertBatch(flavors);}
}

五.套餐管理

新增套餐
@PostMapping@ApiOperation("新增套餐")public Result save(@RequestBody SetmealDTO setmealDTO) {setmealService.saveWithDish(setmealDTO);return Result.success();
}void saveWithDish(SetmealDTO setmealDTO);
@Transactionalpublic void saveWithDish(SetmealDTO setmealDTO) {Setmeal setmeal = new Setmeal();BeanUtils.copyProperties(setmealDTO, setmeal);//向套餐表插入数据setmealMapper.insert(setmeal);//获取生成的套餐idLong setmealId = setmeal.getId();List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();setmealDishes.forEach(setmealDish -> {setmealDish.setSetmealId(setmealId);});//保存套餐和菜品的关联关系setmealDishMapper.insertBatch(setmealDishes);
}<insert id="insert" parameterType="Setmeal" useGeneratedKeys="true" keyProperty="id">insert into setmeal(category_id, name, price, status, description, image, create_time, update_time, create_user, update_user)values (#{categoryId}, #{name}, #{price}, #{status}, #{description}, #{image}, #{createTime}, #{updateTime},#{createUser}, #{updateUser})
</insert>
<insert id="insertBatch" parameterType="list">insert into setmeal_dish(setmeal_id,dish_id,name,price,copies)values<foreach collection="setmealDishes" item="sd" separator=",">(#{sd.setmealId},#{sd.dishId},#{sd.name},#{sd.price},#{sd.copies})</foreach>
</insert>
查询套餐
@GetMapping("/page")
@ApiOperation("分页查询")
public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) {PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);return Result.success(pageResult);
}PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {int pageNum = setmealPageQueryDTO.getPage();int pageSize = setmealPageQueryDTO.getPageSize();PageHelper.startPage(pageNum, pageSize);Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);return new PageResult(page.getTotal(), page.getResult());
}Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
<select id="pageQuery" resultType="com.sky.vo.SetmealVO">selects.*,c.name categoryNamefromsetmeal sleft joincategory cons.category_id = c.id<where><if test="name != null">and s.name like concat('%',#{name},'%')</if><if test="status != null">and s.status = #{status}</if><if test="categoryId != null">and s.category_id = #{categoryId}</if></where>order by s.create_time desc
</select>
修改套餐
@PutMapping@ApiOperation("修改套餐")public Result updateSetmeal(@RequestBody SetmealDTO setmealdto){log.info("修改套餐{}",setmealdto);setmealService.updateSetmeal(setmealdto);return Result.success();
}void updateSetmeal(SetmealDTO setmealdto);
public void updateSetmeal(SetmealDTO setmealdto) {Setmeal setmeal = new Setmeal();BeanUtils.copyProperties(setmealdto, setmeal);//修改套餐信息setmealMapper.updateSetmeal(setmeal);//修改对应的套餐菜品Long setmealId = setmeal.getId();//删除套餐和菜品的关联关系,操作setmeal_dish表,执行deletesetmealDishMapper.deleteBySetmealId(setmealId);List<SetmealDish> setmealDishes = setmealdto.getSetmealDishes();setmealDishes.forEach(setmealDish -> {setmealDish.setSetmealId(setmealId);});//3、重新插入套餐和菜品的关联关系,操作setmeal_dish表,执行insertsetmealDishMapper.insertBatch(setmealDishes);}<update id="updateSetmeal">update setmeal<set><if test="categoryId!=null">category_id = #{categoryId},</if><if test="name!=null">name = #{name},</if><if test="price!=null">price = #{price},</if><if test="description!=null">description = #{description}, </if><if test="image!=null">image = #{image},</if><if test="status!=null">status = #{status},</if></set>where id = #{id}
</update>
删除套餐
@DeleteMapping@ApiOperation("批量删除套餐")public Result delete(@RequestParam List<Long> ids){setmealService.deleteBatch(ids);return Result.success();
}void deleteBatch(List<Long> ids);
public void deleteBatch(List<Long> ids) {//起售中的套餐无法删除for (Long id : ids) {Setmeal setmeal = setmealMapper.getsetmealbyid(id);if(setmeal.getStatus()== StatusConstant.ENABLE){throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);}}for (Long id : ids) {//删除套餐表中的数据setmealMapper.deleteById(id);//删除套餐菜品关系表中的数据setmealDishMapper.deleteBySetmealId(id);}
}@Delete("delete from setmeal where id = #{id}")
void deleteById(Long id);

六.店铺状态设置

@RestController("adminShopController")
@RequestMapping("/admin/shop")
@Slf4j
@Api(tags = "店铺相关接口")
public class ShopController {@Autowiredprivate RedisTemplate redisTemplate;@PutMapping("/{status}")@ApiOperation("管理端设置店铺营业状态")public Result setStatus(@PathVariable Integer status){log.info("设置店铺的营业状态为{}",status == 1 ? "营业中":"打样中");redisTemplate.opsForValue().set("shop_status", status);return Result.success();}@GetMapping("/status")@ApiOperation("获取店铺的营业状态")public Result<Integer> getStatus(){Integer status = (Integer) redisTemplate.opsForValue().get("shop_status");return Result.success(status);}
}@RestController("userShopController")
@RequestMapping("/user/shop")
@Slf4j
@Api(tags = "用户端店铺相关接口")
public class ShopController {@Autowiredprivate RedisTemplate redisTemplate;@GetMapping("/status")@ApiOperation("获取店铺的营业状态")public Result<Integer> getStatus(){Integer status = (Integer) redisTemplate.opsForValue().get("shop_status");return Result.success(status);}
}

七.微信小程序登录

@PostMapping("/login")@ApiOperation("微信登录")public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO) {//微信登录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);
}User wxLogin(UserLoginDTO userLoginDTO);
public User wxLogin(UserLoginDTO userLoginVO) {Map<String, String> params = new HashMap<String, String>();params.put("appid",weChatProperties.getAppid());params.put("secret",weChatProperties.getSecret());params.put("js_code",userLoginVO.getCode());params.put("grant_type","authorization_code");//调用微信接口服务,获得当前用户的openidString res = HttpClientUtil.doGet(WX_LOGIN,params);JSONObject jsonObject = JSONObject.parseObject(res);String openid = jsonObject.getString("openid");//判断openid,空失败if (openid == null || openid.equals("")) {throw new LoginFailedException(MessageConstant.LOGIN_FAILED);}//判断当前用户是否为新用户User user = userMapper.getByUserId(openid);//新用户自动注册if (user == null){user = User.builder().openid(openid).createTime(LocalDateTime.now()).build();userMapper.insert(user);}//返回这个用户对象return user;
}void insert(User user);
<insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into user(openid,name,phone,sex,id_number,avatar,create_time)values (#{openid},#{name},#{phone},#{sex},#{idNumber},#{avatar},#{createTime})
</insert>

八.缓存

缓冲菜品

用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问过大,访问数据库压力很大

通过Redis来缓存菜品数据,减少数据库查询操作

开始-->后端服务-->缓存是否存在-->存在,直接使用,不存在查询数据库

@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {//查询redis中是否存在菜品数据,规则:dish_分类idString key = "dish_" + categoryId;//如果存在,直接返回,无需查询数据库List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);//如果不存在,查询数据库,将查询到的数据放入redisif (list != null) {return Result.success(list);}else{Dish dish = new Dish();dish.setCategoryId(categoryId);dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品List<DishVO> list2 = dishService.listWithFlavor(dish);redisTemplate.opsForValue().set(key, list2);return Result.success(list2);}
}

存在问题,增加,修改,删除菜品以后需要删除缓存

//封装清理缓存数据
private void cleanCache(String pattern){Set keys = redisTemplate.keys(pattern);redisTemplate.delete(keys);
}
缓存套餐
  • Spring Cache

Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单的加一个注解,就能实现缓存功能

Spring Cache底层提供了一层抽象,底层可以切换不同的缓存实现

  • EHCache
  • Caffeine
  • Redis

常用注解

九.购物车管理

添加购物车
@PostMapping("/add")@ApiOperation("添加购物车")public Result addShoppingCart(@RequestBody ShoppingCartDTO shoppingCartDTO) {shoppingCartService.addShoppingCart(shoppingCartDTO);return Result.success();}
void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {ShoppingCart shoppingCart = new ShoppingCart();BeanUtils.copyProperties(shoppingCartDTO, shoppingCart);Long userId = BaseContext.getCurrentId();shoppingCart.setUserId(userId);//判断当前商品是否已经在购物车List<ShoppingCart> list= shoppingMapper.list(shoppingCart);//如果存在,将数量加1if (list.size()>0){ShoppingCart cart = list.get(0);cart.setNumber(cart.getNumber()+1);shoppingMapper.updateNumber(cart);}//如果不存在,将商品加入购物车if (list.size()==0){//判断是否是菜品还是套餐Long dishId = shoppingCartDTO.getDishId();Long setmealId = shoppingCartDTO.getSetmealId();if(dishId != null){Dish dish = dishMapper.geibyid(dishId);shoppingCart.setName(dish.getName());shoppingCart.setImage(dish.getImage());shoppingCart.setAmount(dish.getPrice());shoppingCart.setNumber(1);shoppingCart.setCreateTime(LocalDateTime.now());}else{Setmeal setmeal = setmealMapper.getsetmealbyid(setmealId);shoppingCart.setName(setmeal.getName());shoppingCart.setImage(setmeal.getImage());shoppingCart.setAmount(setmeal.getPrice());shoppingCart.setNumber(1);shoppingCart.setCreateTime(LocalDateTime.now());}shoppingMapper.addShoppingCart(shoppingCart);}}List<ShoppingCart> list(ShoppingCart shoppingCart);
<select id="list" resultType="com.sky.entity.ShoppingCart">select * from shopping_cart<where><if test="userId != null">and user_id=#{userId}</if><if test="setmealId!= null">and setmeal_id=#{setmealId}</if><if test="dishId != null">and dish_id=#{dishId}</if><if test="dishFlavor != null">and dish_flavor=#{dishFlavor}</if></where></select>@Insert("insert into shopping_cart(name,user_id,dish_id,setmeal_id,dish_flavor,number,amount,image,create_time) " +"values (#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{image},#{createTime})")
void addShoppingCart(ShoppingCart shoppingCart);
查看购物车
@GetMapping("/list")@ApiOperation("查看购物车")public Result<List<ShoppingCart>> listShoppingCart() {Long userId = BaseContext.getCurrentId();List<ShoppingCart> shoppingCart = shoppingCartService.getByUserId(userId);return Result.success(shoppingCart);
}List<ShoppingCart> getByUserId(Long userId);
public List<ShoppingCart> getByUserId(Long userId) {List<ShoppingCart> list= shoppingMapper.getByUserId(userId);return list;
}
@Select("select * from shopping_cart where user_id = #{userId}")
List<ShoppingCart> getByUserId(Long userId);
清空购物车
@DeleteMapping("/clean")@ApiOperation("清空购物车")public Result cleanShoppingCart() {Long userId = BaseContext.getCurrentId();shoppingCartService.deleteAll(userId);return Result.success();
}void deleteAll(Long userId);
public void deleteAll(Long userId) {shoppingMapper.deleteAll(userId);
}
@Delete("delete from shopping_cart where user_id = #{userId}")
void deleteAll(Long userId);
删除购物车的某一个商品
@PostMapping("/sub")@ApiOperation("删除购物车中一个商品")public Result sub(@RequestBody ShoppingCartDTO shoppingCartDTO){log.info("删除购物车中一个商品,商品:{}", shoppingCartDTO);shoppingCartService.subShoppingCart(shoppingCartDTO);return Result.success();
}void subShoppingCart(ShoppingCartDTO shoppingCartDTO);
public void subShoppingCart(ShoppingCartDTO shoppingCartDTO) {ShoppingCart shoppingCart = new ShoppingCart();BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);//设置查询条件,查询当前登录用户的购物车数据shoppingCart.setUserId(BaseContext.getCurrentId());List<ShoppingCart> list = shoppingMapper.list(shoppingCart);if(list != null && list.size() > 0){shoppingCart = list.get(0);Integer number = shoppingCart.getNumber();if(number == 1){//当前商品在购物车中的份数为1,直接删除当前记录shoppingMapper.deleteById(shoppingCart.getId());}else {//当前商品在购物车中的份数不为1,修改份数即可shoppingCart.setNumber(shoppingCart.getNumber() - 1);shoppingMapper.updateNumber(shoppingCart);}}
}@Delete("delete from shopping_cart where id = #{id}")
void deleteById(Long id);

十.用户下单

@PostMapping("/submit")
@ApiOperation("用户提交订单")public Result<OrderSubmitVO> addOrder(@RequestBody OrdersSubmitDTO ordersSubmitDTO) {log.info("提交订单{}",ordersSubmitDTO);OrderSubmitVO orderSubmitVO = orderService.submit(ordersSubmitDTO);return Result.success(orderSubmitVO);
}OrderSubmitVO submit(OrdersSubmitDTO ordersSubmitDTO);
@Override
public OrderSubmitVO submit(OrdersSubmitDTO ordersSubmitDTO) {//1.处理业务异常AddressBook addressBook =addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());if(addressBook==null){throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);}Long userId = BaseContext.getCurrentId();ShoppingCart shoppingCart = new ShoppingCart();shoppingCart.setUserId(userId);List<ShoppingCart> shoppingCarts = shoppingMapper.getByUserId(userId);if(shoppingCarts==null){throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);}//2.向订单表插入1条数据Orders orders = new Orders();BeanUtils.copyProperties(ordersSubmitDTO, orders);orders.setUserId(userId);orders.setOrderTime(LocalDateTime.now());orders.setPayStatus(Orders.UN_PAID);orders.setStatus(Orders.PENDING_PAYMENT);orders.setNumber(String.valueOf(System.currentTimeMillis()));orders.setPhone(addressBook.getPhone());orderMapper.insert(orders);//3.向订单明细表插入n条数据List<OrderDetail> orderDetails = new ArrayList<OrderDetail>();for (ShoppingCart cart : shoppingCarts) {OrderDetail orderDetail = new OrderDetail();BeanUtils.copyProperties(cart, orderDetail);orderDetail.setOrderId(orders.getId());orderDetails.add(orderDetail);}orderDetailMapper.insertBatch(orderDetails);//4.清空当前用户的购物出数据shoppingMapper.deleteById(userId);//5.封装VO返回结果OrderSubmitVO orderSubmitVO =OrderSubmitVO.builder().id(orders.getId()).orderTime(orders.getOrderTime()).orderNumber(orders.getNumber()).orderAmount(orders.getAmount()).build();return orderSubmitVO;
}

十一.微信支付

微信支付时序图

cpolar内网穿透
设置authtoken,只需设置一次
cpolar.exe authtoken N2QwNzRlMjctNjFkNC00MmVmLWE5ZTYtY2M4NTcwNDIzZmIw运行cpolar
cpolar.exe http 8080

微信支付相关配置
application.yml
wechat:appid: ${sky.wechat.appid}secret: ${sky.wechat.secret}mchid: ${sky.wechat.mchid}mch-serial-no: ${sky.wechat.mch-serial-no}private-key-file-path: ${sky.wechat.private-key-file-path}api-v3-key: ${sky.wechat.api-v3-key}we-chat-pay-cert-file-path: ${sky.wechat.we-chat-pay-cert-file-path}notify-url: ${sky.wechat.notify-url}refund-notify-url: ${sky.wechat.refund-notify-url}application-dev.ymlwechat:appid: wx831281689c9e6ae9secret: ae3115ef66575e05d2d075e287cb6270mchid: 1561414331mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606privateKeyFilePath: D:\pay\apiclient_key.pemapiV3Key: CZBK51236435wxpay435434323FFDuv3wechatPayCertFilePath: D:\pay\wechatpay_166096F876F45C7D07CE98952A96EC980368ACFC.pemnotifyUrl: https://6f67b0c.r5.cpolar.top//notify/paySuccessrefundNotifyUrl: https://6619cf50.r6.cpolar.top/notify/refundSuccess

http://www.mrgr.cn/news/92026.html

相关文章:

  • 大数据学习之PB级音乐数据中心数仓综合项目(1)-理论知识和项目需求、歌曲热度与歌手热度排行
  • C++:pthread的使用
  • 【Linux】: 传输层协议 TCP
  • Springboot 高频面试题
  • 【洛谷排序算法】P1012拼数-详细讲解
  • 虚拟dom 真实dom
  • ASP.NET Core Clean Architecture
  • Spring Boot 概要(官网文档解读)
  • 我们来学人工智能 -- DeepSeek客户端
  • FPGA DSP:Vivado 中带有 DDS 的 FIR 滤波器
  • 高等数学(上)题型笔记(六)定积分的应用
  • 从零开始用react + tailwindcs + express + mongodb实现一个聊天程序(一)
  • Linux-Ansible模块进阶
  • Windows本地安装ComfyUI
  • 大数据之常用Linux操作
  • 在windows下安装windows+Ubuntu16.04双系统(下)
  • langchain系列 - FewShotPromptTemplate 少量示例
  • 【论文带读(1)】《End-to-End Object Detection with Transformers》论文超详细带读 + 翻译
  • 出行项目案例
  • 1.15作业