文章目录

  • 前言
  • 数据库设计
  • 秒杀商品列表页
  • 秒杀商品详情
  • 实现简单秒杀
  • 订单详情

前言

由于慕课课程中是先实现最基本的功能然后对其压测,压测那个地方出问题,然后在对其优化。所以本文记录的也是实现的是简单的秒杀功能没有涉及到高并发的优化。

数据库设计

1 商品表 包含所有商品的所有信息
2 订单表 包含所有订单的所有信息
3 秒杀商品表 包含秒杀商品的相关信息(id,商品id,商品库存,秒杀价格,开始日期,结束日期)
4 秒杀订单表 包含秒杀订单的相关信息(id,订单id,用户id,商品id)
为什么要这样设计?可不可以保留1和2 ,3和4在1和2中添加一个字段表示?
实际上这样可行,但是不推荐,因为秒杀有很多种类型。今天秒杀,明天促销,后天八折 岂不是每次搞一个活动都要去重新设计表和字段。而且还要修改后端相关代码。这样不利于维护和扩展。 其次我们单独新建一个表,是因为秒杀是多个用户同一时间下订单所以并发量非常大我们需要单独一个表在redis支撑后期。
详细字段可以github上找一下。

秒杀商品列表页

首页秒杀商品列表页其实就是将所有的秒杀商品信息查询出来并返回。

首先在GoodsDao下 查询满足条件的内容

@Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.miaosha_price from miaosha_goods mg left join goods g on mg.goods_id = g.id")public List<GoodsVo> listGoodsVo();

这里需要解释一下可能的疑问点
1.左连接是什么意思?
左连接是保存左表的全部内容,然后按定义的连接条件与右表连接,若右表没有连接规定的数据则对应的字段为null。
2.mg.goods_id = g.id 是连接条件还是查询条件?
是连接条件,也就是按什么条件将两表连接在一起。总不能乱拼,肯定按一定的条件拼接。若要再写查询条件后边只需跟where,这里where作用的是连接后的表。
3.为什么选择左连接?
其实左连接和右连接没有区别,最重要的一点是如果你使用左连接则会保存左表的全部数据,所以左 表一般是右表的子集(或者比右表小),如果左表是更大的,右表是小的,则连接后会发现最后的数据表很多字段为null。在这里秒杀商品是商品的子集

查询出来后我们要新建一个VO对象接受,因为查询出来的字段不仅包含商品字段还会包含秒杀商品的相关字段
所以我们需要在VO目录下新建一个对象

然后在GoodsService下定义

	@AutowiredGoodsDao goodsDao;public List<GoodsVo> listGoodsVo(){return goodsDao.listGoodsVo();}

最后在GoodsController类定义以下方法

    @RequestMapping("/to_list")public String list(Model model,MiaoshaUser user) {// 向goods_list页面添加user对象,至于user是怎么拦截的可以看登录功能model.addAttribute("user", user);//查询商品列表List<GoodsVo> goodsList = goodsService.listGoodsVo();model.addAttribute("goodsList", goodsList);return "goods_list";}

实际上我们写代码是从controller层开始写,这里只是方便展示从dao层开始写。

package com.imooc.miaosha.vo;import java.util.Date;import com.imooc.miaosha.domain.Goods;public class GoodsVo extends Goods{private Double miaoshaPrice;private Integer stockCount;private Date startDate;private Date endDate;public Integer getStockCount() {return stockCount;}public void setStockCount(Integer stockCount) {this.stockCount = stockCount;}public Date getStartDate() {return startDate;}public void setStartDate(Date startDate) {this.startDate = startDate;}public Date getEndDate() {return endDate;}public void setEndDate(Date endDate) {this.endDate = endDate;}public Double getMiaoshaPrice() {return miaoshaPrice;}public void setMiaoshaPrice(Double miaoshaPrice) {this.miaoshaPrice = miaoshaPrice;}
}

疑问点?我们new一个GoodsVo对象可以访问父类的private字段吗?
不能,只能通过get方法访问,即使子类继承了父类。

随后实现good_list前端页面

<div class="panel panel-default"><div class="panel-heading">秒杀商品列表</div><table class="table" id="goodslist"><tr><td>商品名称</td><td>商品图片</td><td>商品原价</td><td>秒杀价</td><td>库存数量</td><td>详情</td></tr><tr  th:each="goods,goodsStat : ${goodsList}">  <td th:text="${goods.goodsName}"></td>  <td ><img th:src="@{${goods.goodsImg}}" width="100" height="100" /></td>  <td th:text="${goods.goodsPrice}"></td>  <td th:text="${goods.miaoshaPrice}"></td>  <td th:text="${goods.stockCount}"></td><td><a th:href="'/goods/to_detail/'+${goods.id}">详情</a></td>  </tr>  </table>
</div>

前端的可以不用详细了解,但是你必须得看懂前端的代码,这个代码就是循环将表格展示出来,循环的内容是我们后端添加的GoodsList对象。我们使用goods接受的对象,后边的是状态可以先不用管。 这里goodsName ,goodsImg貌似是父类的私有字段,为什么可以goods可以访问?
实际上Thymeleaf 访问的是 Java Bean 的 getter 方法,而不是直接访问字段。
即使字段是 private 的,只要它有对应的 public getXxx() 方法(getter),Thymeleaf 就可以访问到这个值。

秒杀商品详情

前面商品列表前端中最后一行我们有一个超链接,是去点击商品详情的。我们需要将商品id通过路径传入。我们的需求是,点进去详情后展示的有基本商品信息和秒杀倒计时以及秒杀按钮。
第一步我们商品详情页面还是需要查询出来具体的商品所以我们首先需要在GoodsController包下定义以下方法

方法中,首先根据商品id查询具体商品信息 并添加到页面中,其次,由于详情页需要展示秒杀的状态所以我们要判断此时秒杀的状态。具体来说根据当前时间和秒杀时间判断

 @RequestMapping("/to_detail/{goodsId}")public String detail(Model model,MiaoshaUser user,@PathVariable("goodsId")long goodsId) {model.addAttribute("user", user);GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);model.addAttribute("goods", goods);//获取秒杀开始时间long startAt = goods.getStartDate().getTime();//获取秒杀结束时间long endAt = goods.getEndDate().getTime();//获取当前时间long now = System.currentTimeMillis();// 秒杀状态 0是未开始 1是进行中 2是已结束int miaoshaStatus = 0;int remainSeconds = 0;//基本条件判断if(now < startAt ) {//秒杀还没开始,倒计时miaoshaStatus = 0;remainSeconds = (int)((startAt - now )/1000);}else  if(now > endAt){//秒杀已经结束miaoshaStatus = 2;remainSeconds = -1;}else {//秒杀进行中miaoshaStatus = 1;remainSeconds = 0;}model.addAttribute("miaoshaStatus", miaoshaStatus);model.addAttribute("remainSeconds", remainSeconds);return "goods_detail";}

1.查询商品信息
在GoodsService下定义以下方法

	public GoodsVo getGoodsVoByGoodsId(long goodsId) {return goodsDao.getGoodsVoByGoodsId(goodsId);}

在GoodsDao下

@Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.miaosha_price from miaosha_goods mg left join goods g on mg.goods_id = g.id where g.id = #{goodsId}")public GoodsVo getGoodsVoByGoodsId(@Param("goodsId")long goodsId);

其查询代码的含义是 按着查询条件查询拼接完成的代码,参数我们需要传递到sql语句中所以需要用@param将参数传递过去

2.秒杀基本条件判断
这里代码中有注释且逻辑较为简单。我们只需记住后端传给前端了秒杀商品的状态和此刻的剩余时间。

3.前端逻辑处理

<div class="panel panel-default"><div class="panel-heading">秒杀商品详情</div><div class="panel-body">//判断user是否为空<span th:if="${user eq null}"> 您还没有登录,请登陆后再操作<br/></span><span>没有收货地址的提示。。。</span></div><table class="table" id="goodslist"><tr>  <td>商品名称</td>  <td colspan="3" th:text="${goods.goodsName}"></td> </tr>  <tr>  <td>商品图片</td>  <td colspan="3"><img th:src="@{${goods.goodsImg}}" width="200" height="200" /></td>  </tr><tr>  <td>秒杀开始时间</td>  <td th:text="${#dates.format(goods.startDate, 'yyyy-MM-dd HH:mm:ss')}"></td><td id="miaoshaTip">	<input type="hidden" id="remainSeconds" th:value="${remainSeconds}" /><span th:if="${miaoshaStatus eq 0}">秒杀倒计时:<span id="countDown" th:text="${remainSeconds}"></span></span><span th:if="${miaoshaStatus eq 1}">秒杀进行中</span><span th:if="${miaoshaStatus eq 2}">秒杀已结束</span></td><td><form id="miaoshaForm" method="post" action="/miaosha/do_miaosha">//点击后会向地址/miaosha/do_miaosha post goods_id<button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒杀</button><input type="hidden" name="goodsId" th:value="${goods.id}" /></form></td></tr><tr>  <td>商品原价</td>  <td colspan="3" th:text="${goods.goodsPrice}"></td>  </tr><tr>  <td>秒杀价</td>  <td colspan="3" th:text="${goods.miaoshaPrice}"></td>  </tr><tr>  <td>库存数量</td>  <td colspan="3" th:text="${goods.stockCount}"></td>  </tr></table>
</div>
</body>
<script>
$(function(){countDown();
});function countDown(){var remainSeconds = $("#remainSeconds").val();var timeout;//判断基本条件if(remainSeconds > 0){//秒杀还没开始,倒计时$("#buyButton").attr("disabled", true);timeout = setTimeout(function(){$("#countDown").text(remainSeconds - 1);$("#remainSeconds").val(remainSeconds - 1);//这里倒计时需要不断地减少时间所以需要回调函数countDown();},1000);}else if(remainSeconds == 0){//秒杀进行中$("#buyButton").attr("disabled", false);if(timeout){clearTimeout(timeout);}$("#miaoshaTip").html("秒杀进行中");}else{//秒杀已经结束$("#buyButton").attr("disabled", true);$("#miaoshaTip").html("秒杀已经结束");}
}</script>
</html>

实现简单秒杀

最后我们可以实现秒杀功能主要包含三部分
1减库存 2下订单 3写入秒杀订单.这三步必须在一个事务内部实现,因为如果有一个失败了就只能全部失败。

    @RequestMapping("/do_miaosha")public String list(Model model,MiaoshaUser user,@RequestParam("goodsId")long goodsId) {model.addAttribute("user", user);if(user == null) {return "login";}//判断库存,这里查询goods有两个目的一个是判断库存另一个是写入商品相关信息。这里虽然是goods但是联表查询的goodsVoGoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);int stock = goods.getStockCount();if(stock <= 0) {model.addAttribute("errmsg", CodeMsg.MIAO_SHA_OVER.getMsg());return "miaosha_fail";}//判断是否已经秒杀到了MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);if(order != null) {model.addAttribute("errmsg", CodeMsg.REPEATE_MIAOSHA.getMsg());return "miaosha_fail";}//减库存 下订单 写入秒杀订单OrderInfo orderInfo = miaoshaService.miaosha(user, goods);model.addAttribute("orderInfo", orderInfo);model.addAttribute("goods", goods);return "order_detail";}

判断库存这一步就是根据商品id查询商品,随后判断库存是否小于0。不过多赘述

第二部判断用户是否秒杀过此商品了?因为每个秒杀商品用户只能秒杀一次,所以需要判断。我们判断订单表中用户是否秒杀此商品,因此我们的条件要有两个一个是用户id一个是商品id。缺一不可

@Select("select * from miaosha_order where user_id=#{userId} and goods_id=#{goodsId}")public MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(@Param("userId")long userId, @Param("goodsId")long goodsId);

我们只需要在dao层加入相关代码。

随后就是核心代码我们首先创建miaosha对象

package com.imooc.miaosha.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import com.imooc.miaosha.domain.MiaoshaUser;
import com.imooc.miaosha.domain.OrderInfo;
import com.imooc.miaosha.vo.GoodsVo;@Service
public class MiaoshaService {@AutowiredGoodsService goodsService;@AutowiredOrderService orderService;@Transactionalpublic OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {//减库存 下订单 写入秒杀订单goodsService.reduceStock(goods);//order_info maiosha_orderreturn orderService.createOrder(user, goods);}}

首先这是一个事务必须定义@Transactional
1.减库存
在我们对应秒杀商品数据库下的库存–,首先我们需要知道是那个商品。

	public void reduceStock(GoodsVo goods) {MiaoshaGoods g = new MiaoshaGoods();g.setGoodsId(goods.getId());goodsDao.reduceStock(g);}

这里为啥要new一个秒杀商品呢?因为我们传过来的参数是GoodsVo 在数据库并没有表对应此类型,所以需要new秒杀商品对象,随后传入商品的id,dao利用id对其更新库存。其实我个人感觉对于此更新方法直接传一个goodsId然后用@Param绑定不就好了为什么非要传一个对象?
dao层的实现

@Update("update miaosha_goods set stock_count = stock_count - 1 where goods_id = #{goodsId}")public int reduceStock(MiaoshaGoods g);

疑问点:为什么更新操作的返回值是int?这是因为更新操作会返回更新成功的行数

生成订单分为两步1.写orderinfo 2.写秒杀order 具体业务逻辑

package com.imooc.miaosha.service;import java.util.Date;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import com.imooc.miaosha.dao.OrderDao;
import com.imooc.miaosha.domain.MiaoshaOrder;
import com.imooc.miaosha.domain.MiaoshaUser;
import com.imooc.miaosha.domain.OrderInfo;
import com.imooc.miaosha.vo.GoodsVo;@Service
public class OrderService {@AutowiredOrderDao orderDao;public MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(long userId, long goodsId) {return orderDao.getMiaoshaOrderByUserIdGoodsId(userId, goodsId);}@Transactionalpublic OrderInfo createOrder(MiaoshaUser user, GoodsVo goods) {OrderInfo orderInfo = new OrderInfo();//将订单相关信息写入orderInfo.setCreateDate(new Date());orderInfo.setDeliveryAddrId(0L);orderInfo.setGoodsCount(1);orderInfo.setGoodsId(goods.getId());orderInfo.setGoodsName(goods.getGoodsName());orderInfo.setGoodsPrice(goods.getMiaoshaPrice());orderInfo.setOrderChannel(1);orderInfo.setStatus(0);orderInfo.setUserId(user.getId());//生成订单,这里要获取订单id然后写入秒杀订单里面long orderId = orderDao.insert(orderInfo);//秒杀订单信息吸入MiaoshaOrder miaoshaOrder = new MiaoshaOrder();miaoshaOrder.setGoodsId(goods.getId());miaoshaOrder.setOrderId(orderId);miaoshaOrder.setUserId(user.getId());//生成秒杀订单orderDao.insertMiaoshaOrder(miaoshaOrder);return orderInfo;}}

随后在dao层写入代码

@Insert("insert into order_info(user_id, goods_id, goods_name, goods_count, goods_price, order_channel, status, create_date)values("+ "#{userId}, #{goodsId}, #{goodsName}, #{goodsCount}, #{goodsPrice}, #{orderChannel},#{status},#{createDate} )")@SelectKey(keyColumn="id", keyProperty="id", resultType=long.class, before=false, statement="select last_insert_id()")public long insert(OrderInfo orderInfo);

疑问点:这里为什么也要加@Transactional ?外层不是加了吗?
其实单说秒杀外层函数加@Transactional其实够了,在里边加注解是为了防止别的方法调用此函数时形成不一致的情况。

疑问点:插入订单时如何返回订单id的?
用Mybatis中SelectKey注解,具体解释如下
@SelectKey(
keyColumn = “id”, // 数据库中自增主键的列名
keyProperty = “id”, // Java 对象中对应的属性名
resultType = long.class, // 主键的 Java 类型
before = false, // 表示在 insert 语句执行“之后”再执行 select last_insert_id()
statement = “select last_insert_id()” // 执行的 SQL 语句,用于获取最近插入记录的自增主键
)
在执行 @Insert 插入操作后,自动执行一条 SQL(这里是 select last_insert_id()),把插入成功后的自增主键值写入你传入对象(orderInfo)的某个属性中(这里是 id)。

随后在将相关信息写入秒杀订单表

	@Insert("insert into miaosha_order (user_id, goods_id, order_id)values(#{userId}, #{goodsId}, #{orderId})")public int insertMiaoshaOrder(MiaoshaOrder miaoshaOrder);

订单详情

最后我们返回的是订单页
我们只需要将后端的订单信息和商品信息传入到前端,前端按一定的形式展示即可。

<div class="panel panel-default"><div class="panel-heading">秒杀订单详情</div><table class="table" id="goodslist"><tr>  <td>商品名称</td>  <td th:text="${goods.goodsName}" colspan="3"></td> </tr>  <tr>  <td>商品图片</td>  <td colspan="2"><img th:src="@{${goods.goodsImg}}" width="200" height="200" /></td>  </tr><tr>  <td>订单价格</td>  <td colspan="2" th:text="${orderInfo.goodsPrice}"></td>  </tr><tr><td>下单时间</td>  <td th:text="${#dates.format(orderInfo.createDate, 'yyyy-MM-dd HH:mm:ss')}" colspan="2"></td>  </tr><tr><td>订单状态</td>  <td ><span th:if="${orderInfo.status eq 0}">未支付</span><span th:if="${orderInfo.status eq 1}">待发货</span><span th:if="${orderInfo.status eq 2}">已发货</span><span th:if="${orderInfo.status eq 3}">已收货</span><span th:if="${orderInfo.status eq 4}">已退款</span><span th:if="${orderInfo.status eq 5}">已完成</span></td>  <td><button class="btn btn-primary btn-block" type="submit" id="payButton">立即支付</button></td></tr><tr><td>收货人</td>  <td colspan="2">XXX  18812341234</td>  </tr><tr><td>收货地址</td>  <td colspan="2">北京市昌平区回龙观龙博一区</td>  </tr></table>
</div>

今天这一节主要实现的是简单的秒杀,后续还会进行优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/89132.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/89132.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/89132.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React 的常用钩子函数在Vue中是如何设计体现出来的。

1、定义响应式数据&#xff1a; React 通过 useState 和 useReducer Vue 通过 ref 和 reactiveconst [state, setState] useState(initialState)const [state, dispatch] useReducer(reducer, initialState)2、定义缓存数据&#xff1a; React 通过 memo 和 useMemo useCal…

开源的 H.264/AVC 视频编码器库-x264 的交叉编译 和 程序测试

一、环境准备 安装交叉编译工具链 根据目标ARM架构选择对应工具链&#xff08;如arm-linux-gnueabihf-&#xff09;&#xff1a;# Ubuntu/Debian系统 sudo apt-get install gcc-arm-linux-gnueabihf g-arm-linux-gnueabihf# 验证安装 arm-linux-gnueabihf-gcc --version或者手动…

自由学习记录(69)

RectToPolar() 是 将直角坐标系 (笛卡尔坐标系) 的 uv 坐标&#xff0c;转化为极坐标系&#xff08;θ&#xff0c;r&#xff09; uv - centerUV&#xff1a;将坐标原点平移&#xff0c;使 (0.5, 0.5) 变成原点。 r length(uv)&#xff1a;距离中心点的半径&#xff08;从中…

Spring Boot 敏感信息入库加密全面解决方案

Spring Boot 敏感信息入库加密全面解决方案 在当今数据驱动的时代,保护用户隐私数据已成为系统设计的必备要求。本文将详细介绍 Spring Boot 应用中敏感数据加密存储的完整方案,涵盖从基础实现到生产级落地的全流程。 一、加密方案选型 1.1 常见加密类型对比 加密类型特点…

docker0网卡没有ip一步解决

正常查看ip的时候一直显示没有ip这里先删除docker0网卡ip link delete docker0然后重启服务systemctl restart docker再次查看显示有ip了并且查看配置文件也是正常的cat /etc/docker/daemon.json {"registry-mirrors": ["https://docker.m.daocloud.io",&q…

MYSQL-索引篇

索引结构概述MySQL 的索引是在存储引擎层实现的&#xff0c;不同的存储引擎有不同的索引结构&#xff0c;主要包含以下几种&#xff1a;索引结构描述BTree索引最常见的索引类型&#xff0c;大部分引擎都支持 B 树索引Hash索引底层数据结构是用哈希表实现的&#xff0c;只有精确…

(纯新手教程)HTML零基础教学

&#xff08;下一章&#xff1a;python网络爬虫&#xff09;HTML 简介HTML&#xff08;HyperText Markup Language&#xff0c;超文本标记语言&#xff09;是用于创建网页的标准标记语言。什么是 HTML&#xff1f;HTML 不是编程语言&#xff0c;而是一种标记语言使用标签来描述…

前端面试宝典---项目难点2-智能问答对话框采用虚拟列表动态渲染可视区域元素(10万+条数据)

引言 在我参与智能问答项目中一个智能体回话并不会像豆包一样&#xff0c;每次新建会话都是是从头开始&#xff0c;而项目中你想创建新会话就像chatbox一样&#xff0c;是点击橡皮擦开启新的聊天上下文&#xff0c;但是直接的聊天记录依然存在&#xff0c;针对超过十万&#xf…

Python元组:不可变数据的强大用法

文章目录元组概念1.基本特性2.创建元组3.访问元素4.元组的不可变性5.元组操作6.元组解包7.命名元组8.元组与列表的比较9.元组的优势10.适用场景11.常用方法小结元组 概念 元组是 Python 中一个非常重要的内置数据结构&#xff0c;它与列表(list)相似但具有关键差异。下面我将…

若尔盖湿地的花湖

花湖位于若尔盖县和甘肃的郎木寺之间的213国道旁&#xff0c;属于若尔盖湿地国家级自然保护区内。又名“梅朵湖”&#xff0c;因阳光照射下湖面色彩斑斓如绚丽的花瓣得名。花湖的大门是梯形高大石柱搭成&#xff0c;我们需要过天桥到对面检票坐小交通。通过车窗看到一层一层的云…

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | DoubleClickHeart(双击爱心)

&#x1f4c5; 我们继续 50 个小项目挑战&#xff01;—— DoubleClickHeart组件 仓库地址&#xff1a;https://github.com/SunACong/50-vue-projects 项目预览地址&#xff1a;https://50-vue-projects.vercel.app/ 使用 Vue 3 的 Composition API&#xff08;<script se…

1-绪论-1-数据结构的基本概念

&#x1f389; 数据结构的魔法世界&#x1f4da;&#x1f468;‍&#x1f393;“数据就像大海中的浪花&#xff0c;结构则是那神秘的洋流。掌握数据结构&#xff0c;就是掌握在信息海洋中自由航行的力量&#xff01;”引言&#xff1a;为什么要学数据结构&#xff1f;&#x1f…

linux网络相关命令简介

目录 一、IP命令 1、Link或L:管理网络接口(网卡) 2、Address或Addr,A:管理Ip地址 3、Route或R:管理路由表

教育培训机构如何为课程视频添加防盗录的强水印?

在知识付费时代&#xff0c;教育培训机构的课程视频是核心资产&#xff0c;但盗录、非法传播等问题却让机构防不胜防。如何在不影响学员观看体验的前提下&#xff0c;为课程视频添加“强效防盗水印”&#xff0c;精准追踪泄露源头&#xff1f;本文将为您揭秘高安全性水印的添加…

python的形成性考核管理系统

前端开发框架:vue.js 数据库 mysql 版本不限 后端语言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 数据库工具&#xff1a;Navicat/SQLyog等都可以 摘要 随着…

A*算法详解

A*算法详解一、A*算法基础概念1.1 算法定位1.2 核心评估函数1.3 关键数据结构二、A*算法的核心步骤三、启发函数设计3.1 网格地图中的启发函数3.2 启发函数的选择原则三、Java代码实现四、启发函数的设计与优化4.1 启发函数的可采纳性4.2 启发函数的效率影响4.3 常见启发函数对…

.net winfrom 获取上传的Excel文件 单元格的背景色

需求&#xff1a;根据Excel某行标注了黄色高亮颜色&#xff0c;说明该行数据已被用户选中(Excel文件中并没有“已选中”这一列&#xff0c;纯粹用颜色表示)&#xff0c;导入数据到数据库时标注此行已选中直接上代码&#xff1a;//选择Excel文件private void btnBrowse_Click(ob…

OpenAI GPT-4o技术详解:全能多模态模型的架构革新与生态影响

本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; ⚙️ 一、核心定义与发布背景 官方定位 GPT-4o&#xff08;“o”代表“…

⚡ 构建真正的高性能即时通讯服务:基于 Netty 集群的架构设计与实现

引子 在前面的文章中&#xff0c;我们基于 Netty 构建了一套单体架构的即时通讯服务。虽然单体架构在开发初期简单高效&#xff0c;但随着用户量的增长和业务规模的扩大&#xff0c;其局限性逐渐显现。当面对高并发场景时&#xff0c;单体 Netty 服务很容易触及性能天花板&…

原来时间序列挖掘这么简单

先搞懂&#xff1a;啥是时间序列&#xff1f;简单说&#xff0c;时间序列就是按时间顺序记下来的数据。比如&#xff1a;你每天早上 8 点测的体重&#xff0c;连起来就是 “体重时间序列”&#xff1b;超市每天的销售额&#xff0c;连起来就是 “销售时间序列”&#xff1b;城市…