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

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

相关文章:

SpringBoot+Vue+Mysql苍穹外卖

一.项目介绍 1.项目内容 苍穹外卖是一款为大学学子设计的校园外卖服务软件&#xff0c;旨在提供便捷的食堂外卖送至宿舍的服务。该软件包含系统管理后台和用户端&#xff08;微信小程序&#xff09;两部分&#xff0c;支持在线浏览菜品、添加购物车、下单等功能&#xff0c;并…...

网络运维学习笔记 018 HCIA-Datacom综合实验02

文章目录 综合实验2sw3&#xff1a;sw4&#xff1a;gw&#xff1a;core1&#xff08;sw1&#xff09;&#xff1a;core2&#xff08;sw2&#xff09;&#xff1a;ISP 综合实验2 sw3&#xff1a; vlan 2 stp mode stp int e0/0/1 port link-type trunk port trunk allow-pass v…...

在 Java 中解析 JSON 数据

例子解析以下JSON数据 {"code":0,"msg":"成功","data": [{ "host":"1068222.com", "port":"", "m_token":"490e20e70e7de5f21a24b14c12a393f6", "categ…...

QT 引入Quazip和Zlib源码工程到项目中,无需编译成库,跨平台,压缩进度

前言 最近在做项目时遇到一个需求&#xff0c;需要将升级的文件压缩成zip&#xff0c;再进行传输&#xff1b; 通过网络调研&#xff0c;有许多方式可以实现&#xff0c;例如QT私有模块的ZipReader、QZipWriter&#xff1b;或者第三方库zlib或者libzip或者quazip等&#xff1…...

C++ 互斥锁的使用

mutex std::mutex 是C标准库中用于线程同步的互斥锁机制&#xff0c;主要用于保护共享资源&#xff0c;避免多个线程同时访问导致的竞态条件。 它提供了以下功能&#xff1a; 加锁&#xff08;lock&#xff09;&#xff1a;阻塞当前线程&#xff0c;直到获取锁。 解锁&#…...

使用 deepseek实现 go语言,读取文本文件的功能,要求支持 ascii,utf-8 等多种格式自适应

使用 deepseek实现 go语言&#xff0c;读取文本文件的功能&#xff0c;要求支持 ascii&#xff0c;utf-8 等多种格式自适应我要用 chatgpt&#xff0c;也问过&#xff0c;但是比 deepseek 还是差一个级别&#xff0c;具体如下&#xff1a; package mainimport ("bufio&qu…...

车载诊断架构 --- LIN节点路由转发注意事项

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…...

Eclipse2024中文汉化教程(图文版)

对应Eclipse,部分人需要中文汉化,本章教程,介绍如何对Eclipse进行汉化的具体步骤。 一、汉化前的Eclipse 默认安装Eclipse的时候,默认一般都是English的,我当前版本是使用的是2024-06版本的Eclipse。 二、汉化详细步骤 点击上方菜单选项卡,Hep——Install New Software……...

网络协议相关知识有哪些?

前言 网络协议的基础是OSI和TCP/IP模型,这两个模型是理解协议分层的关键。 正文(仅是个人理解,如有遗漏望海涵) 网络协议是网络中设备间通信的规则和标准,涉及数据传输、路由、错误控制等多个方面。以下是网络协议相关知识的系统梳理: 一、网络协议分层模型 1、OSI七…...

医院安全(不良)事件上报系统源码,基于Laravel8开发,依托其优雅的语法与强大的扩展能力

医院安全&#xff08;不良&#xff09;事件上报系统源码 系统定义&#xff1a; 规范医院安全&#xff08;不良&#xff09;事件的主动报告&#xff0c;增强风险防范意识&#xff0c;及时发现医院不良事件和安全隐患&#xff0c;将获取的医院安全信息进行分析反馈&#xff0c;…...

【第一节】C++设计模式(创建型模式)-工厂模式

目录 前言 一、面向对象的两类对象创建问题 二、解决问题 三、工厂模式代码示例 四、工厂模式的核心功能 五、工厂模式的应用场景 六、工厂模式的实现与结构 七、工厂模式的优缺点 八、工厂模式的扩展与优化 九、总结 前言 在面向对象系统设计中&#xff0c;开发者常…...

分发糖果(力扣135)

题目说相邻的两个孩子中评分更高的孩子获得的糖果更多&#xff0c;表示我们既要考虑到跟左边的孩子比较&#xff0c;也要考虑右边的孩子&#xff0c;但是我们如果两边一起考虑一定会顾此失彼。这里就引入一个思想&#xff1a;先满足右边大于左边时的糖果分发情况&#xff0c;再…...

爬虫小案例豆瓣电影top250(json格式)

1.json格式&#xff08;仅供学习参考&#xff09; import requests, json, jsonpathclass Start(object):# 类实例化时会执行def __init__(self):self.headers {user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.…...

RTSP场景下RTP协议详解及音视频打包全流程

RTSP场景下RTP协议详解及音视频打包全流程 一、RTSP与RTP的关系 RTSP&#xff1a;负责媒体会话控制&#xff08;DESCRIBE、SETUP、PLAY、PAUSE&#xff09;&#xff0c;通过SDP协商传输参数&#xff08;端口、编码格式、封装模式&#xff09;。RTP&#xff1a;实际传输音视频数…...

关于Transparent native-to-ascii conversion

1、功能 自动转换ASCII编码&#xff0c;即在文件系统上&#xff0c;文件的编码格式为ascii编码&#xff0c;在编辑器&#xff08;idea/pycharm&#xff09;中&#xff0c;其展现结果为配置的编码格式&#xff0c;仅展现方便阅读 使用UTF-8并勾选自动转换ASCII编码结果&#x…...

万字长文解析:深入理解服务端渲染(SSR)架构与全栈实践指南

一、SSR核心原理深度剖析 1.1 技术定义与演进历程 服务端渲染&#xff08;Server-Side Rendering&#xff09;指在服务器端完成页面DOM构建的技术方案。其发展历程可分为三个阶段&#xff1a; 阶段时期典型技术传统SSR2000-2010JSP/PHP现代SSR2015-2020Next.js/Nuxt.js混合渲…...

Spring事务原理 二

在上一篇博文《Spring事务原理 一》中&#xff0c;我们熟悉了Spring声明式事务的AOP原理&#xff0c;以及事务执行的大体流程。 本文中&#xff0c;介绍了Spring事务的核心组件、传播行为的源码实现。下一篇中&#xff0c;我们将结合案例&#xff0c;来讲解实战中有关事务的易…...

SpringAI系列 - ToolCalling篇(二) - 如何设置应用侧工具参数ToolContext(有坑)

目录 一、引言二、集成ToolContext示例步骤1: 在`@Tool`标注的工具方法中集成`ToolConext`参数步骤2:`ChatClient`运行时动态设置`ToolContext`参数三、填坑一、引言 在使用AI大模型的工具调用机制时,工具参数都是由大模型解析用户输入上下文获取的,由大模型提供参数给本地…...

本地部署MindSearch(开源 AI 搜索引擎框架),然后上传到 hugging face的Spaces——L2G6

部署MindSearch到 hugging face Spaces上——L2G6 任务1 在 官方的MindSearch页面 复制Spaces应用到自己的Spaces下&#xff0c;Space 名称中需要包含 MindSearch 关键词&#xff0c;请在必要的步骤以及成功的对话测试结果当中 实现过程如下&#xff1a; 2.1 MindSearch 简…...

MyBatis Plus扩展功能

一、代码生成器 二、逻辑删除 三、枚举处理器 像状态字段我们一般会定义一个枚举&#xff0c;做业务判断的时候就可以直接基于枚举做比较。但是我们数据库采用的是int类型&#xff0c;对应的PO也是Integer。因此业务操作时必须手动把枚举与Integer转换&#xff0c;非常麻烦。 …...

深度学习之自然语言处理CBOW预测及模型的保存

自然语言处理CBOW预测及模型的保存 目录 自然语言处理CBOW预测及模型的保存1 自然语言处理1.1 概念1.2 词向量1.2.1 one-hot编码1.2.2 词嵌入1.2.3 常见的词嵌入模型 2 CBOW预测模型搭建2.1 数据及模型确定2.1.1 数据2.1.2 CBOW模型2.1.3 词嵌入降维 2.2 数据预处理2.3 模型搭建…...

qt项目配置部署

Test项目: 子项目testFileHelper 1.新建一个test项目的子项目:取名testFileHelper 2.编写测试用例 3.pro文件中引入qosbrowser 4.引入测试对象的cpp和头文件 2.在项目中引入资源文件testfile.txt,在其中输入abc 实现thrid目录复用 移动thrid 将thrild目录统一放在章…...

java方法学习

java 方法 在Java中&#xff0c;方法是类&#xff08;或对象&#xff09;的行为或功能的实现。&#xff08;一起实现一个功能&#xff09;java的方法类似于其他语言的函数&#xff0c;是一段用来完成特定功能的代码片段。 方法是解决一类问题步骤的有序结合。 方法包含于类或…...

基于vue和微信小程序的校园自助打印系统(springboot论文源码调试讲解)

第3章 系统设计 3.1系统功能结构设计 本系统的结构分为管理员和用户、店长。本系统的功能结构图如下图3.1所示&#xff1a; 图3.1系统功能结构图 3.2数据库设计 本系统为小程序类的预约平台&#xff0c;所以对信息的安全和稳定要求非常高。为了解决本问题&#xff0c;采用前端…...

解析CV/多模态算法的要点及技术特点,弥补单模态信息不足的多模态应用的哪些场景中?

CV&#xff08;计算机视觉&#xff09;多模态算法是计算机科学领域的重要研究方向&#xff0c;融合了多种模态的数据来提升对视觉信息的理解和处理能力。 以下是一个结合自动驾驶行业的多模态大模型算法示例&#xff0c;采用特征级融合策略&#xff0c;结合摄像头图像和激光雷…...

[漏洞篇]文件上传漏洞详解

[漏洞篇]文件上传漏洞详解 一、介绍 1. 概念 文件上传漏洞是指用户上传了一个可执行的脚本文件&#xff0c;并通过此脚本文件获得了执行服务器端命令的能力。这种攻击方式是最为直接和有效的&#xff0c;“文件上传” 本身没有问题&#xff0c;有问题的是文件上传后&#xf…...

11.Docker 之分布式仓库 Harbor

Docker 之分布式仓库 Harbor Docker 之分布式仓库 Harbor1. Harbor 组成2. 安装 Harbor Docker 之分布式仓库 Harbor Harbor 是一个用于存储和分发 Docker 镜像的企业级 Registry 服务器&#xff0c;由 VMware 开源&#xff0c;其通过添加一些企业必需的功能特性&#xff0c;例…...

Python项目源码34:网页内容提取工具1.0(Tkinter+requests+html2text)

------★Python练手项目源码★------- Python项目32&#xff1a;订单销售额管理系统1.0&#xff08;TkinterCSV&#xff09; Python项目31&#xff1a;初学者也能看懂的聊天机器人1.0源码&#xff08;命令行界面Re正则表达式&#xff09; Python项目源码30&#xff1a;待办事…...

使用Termux将安卓手机变成随身AI服务器(page assist连接)

通过以下方法在安卓手机上运行 Ollama 及大模型&#xff0c;无需 Root 权限&#xff0c;具体方案如下&#xff1a; 通过 Termux 模拟 Linux 环境运行 核心工具&#xff1a; 安装 &#xff08;安卓终端模拟器&#xff09;()]。借助 proot-distro 工具安装 Linux 发行版&#xf…...

SpringBoot3中跨域问题解决

问题 SpringBoot3 中处理跨域请求 异常 浏览器在 localhost:3000 地址请求后端 http://127.0.0.1:8080 时, 报错提示 CORS 问题. 默认使用 Get 请求正常, 其他会提示. 使用 SpringBoot 3.4.2 版本配合 SpringSecurity 配置 Access to fetch at http://127.0.0.1:8080/todo-…...