[苍穹外卖]-08微信支付详解
地址簿管理
分析需求: 查询地址列表/新增地址/修改地址/删除地址/设置默认地址/查询默认地址
接口设计
- 新增地址接口
- 查询用户所有的地址接口
- 查询默认地址接口
- 根据id修改地址接口
- 根据id删除地址接口
- 根据id查询地址接口
- 设置默认地址接口
数据库设计: 收货地址簿(address_book表)
代码导入
功能测试
用户下单
需求分析和设计
在电商系统中,用户是通过下单的方式通知商家, 用户已经购买了商品, 需要商家备货和发货
用户下单后会产生订单相关数据, 订单数据需要有: 商品名称/商品数量/订单金额/用户名称/手机号/收货地址
接口分析
接口设计
数据库设计
代码开发
设计VO: 根据用户下单接口返回的数据设计OrderSubmitVO, 用户封装返回给前端的数据
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderSubmitVO implements Serializable {//订单idprivate Long id;//订单号private String orderNumber;//订单金额private BigDecimal orderAmount;//下单时间private LocalDateTime orderTime;
}
设计DTO: 根据用户下单接口的参数设计OrdersSubmitDTO, 用于封装前端传递的参数
@Data
public class OrdersSubmitDTO implements Serializable {//地址簿idprivate Long addressBookId;//付款方式private int payMethod;//备注private String remark;//预计送达时间@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime estimatedDeliveryTime;//配送状态 1立即送出 0选择具体时间private Integer deliveryStatus;//餐具数量private Integer tablewareNumber;//餐具数量状态 1按餐量提供 0选择具体数量private Integer tablewareStatus;//打包费private Integer packAmount;//总金额private BigDecimal amount;
}
Controller: 新建user/OrderController
@RestController("userOrderController")
@RequestMapping("/user/order")
@Slf4j
@Api(tags = "用户端订单相关接口")
public class OrderController {@Autowiredprivate OrderService orderService;@PostMapping("/submit")@ApiOperation("用户下单")public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO) {log.info("用户下单, 数据: {}", ordersSubmitDTO);OrderSubmitVO orderSubmitVO = orderService.submitOrder(ordersSubmitDTO);return Result.success(orderSubmitVO);}
}
设计Orders订单实体类, 用于封装插入到订单表的数据, 字段与订单表一致
/*** 订单*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Orders implements Serializable {/*** 订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消*/public static final Integer PENDING_PAYMENT = 1;public static final Integer TO_BE_CONFIRMED = 2;public static final Integer CONFIRMED = 3;public static final Integer DELIVERY_IN_PROGRESS = 4;public static final Integer COMPLETED = 5;public static final Integer CANCELLED = 6;/*** 支付状态 0未支付 1已支付 2退款*/public static final Integer UN_PAID = 0;public static final Integer PAID = 1;public static final Integer REFUND = 2;private static final long serialVersionUID = 1L;private Long id;//订单号private String number;//订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消 7退款private Integer status;//下单用户idprivate Long userId;//地址idprivate Long addressBookId;//下单时间private LocalDateTime orderTime;//结账时间private LocalDateTime checkoutTime;//支付方式 1微信,2支付宝private Integer payMethod;//支付状态 0未支付 1已支付 2退款private Integer payStatus;//实收金额private BigDecimal amount;//备注private String remark;//用户名private String userName;//手机号private String phone;//地址private String address;//收货人private String consignee;//订单取消原因private String cancelReason;//订单拒绝原因private String rejectionReason;//订单取消时间private LocalDateTime cancelTime;//预计送达时间private LocalDateTime estimatedDeliveryTime;//配送状态 1立即送出 0选择具体时间private Integer deliveryStatus;//送达时间private LocalDateTime deliveryTime;//打包费private int packAmount;//餐具数量private int tablewareNumber;//餐具数量状态 1按餐量提供 0选择具体数量private Integer tablewareStatus;
}
设计Orders订单明细实体类, 用于封装插入到订单明细表的数据, 字段与订单明细表一致
/*** 订单明细*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderDetail implements Serializable {private static final long serialVersionUID = 1L;private Long id;//名称private String name;//订单idprivate Long orderId;//菜品idprivate Long dishId;//套餐idprivate Long setmealId;//口味private String dishFlavor;//数量private Integer number;//金额private BigDecimal amount;//图片private String image;
}
Service: 新建OrderService接口和实现类
@Service
public interface OrderService {/*** 用户下单* @param ordersSubmitDTO* @return*/OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO);
}
@Service
public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate OrderDetailMapper orderDetailMapper;@Autowiredprivate AddressBookMapper addressBookMapper;@Autowiredprivate ShoppingCartMapper shoppingCartMapper;/*** 用户下单** @param ordersSubmitDTO* @return*/@Transactionalpublic OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {// 处理可能的业务异常(地址簿为空, 购物车为空)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> list = shoppingCartMapper.list(shoppingCart);if (list.size() == 0 && list == null) {// 购物车为空,抛出业务异常throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);}// 向订单表插入1条数据Orders orders = new Orders();BeanUtils.copyProperties(ordersSubmitDTO, orders);orders.setOrderTime(LocalDateTime.now());orders.setPayStatus(Orders.UN_PAID);orders.setStatus(Orders.PENDING_PAYMENT);orders.setNumber(String.valueOf(System.currentTimeMillis()));orders.setPhone(addressBook.getPhone());orders.setConsignee(addressBook.getConsignee());orders.setUserId(userId);orderMapper.insert(orders);// 向订单明细表插入n条数据ArrayList<OrderDetail> orderDetailList = new ArrayList<>();for (ShoppingCart cart : list) {OrderDetail orderDetail = new OrderDetail(); // 订单明细BeanUtils.copyProperties(cart, orderDetail);orderDetail.setOrderId(orders.getId()); // 设置当前明细关联的订单idorderDetailList.add(orderDetail);}orderDetailMapper.insertBatch(orderDetailList);// 清空当前用户的购物车数据shoppingCartMapper.deleteByUserId(userId);// 封装VO返回结果OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder().id(orders.getId()).orderTime(orders.getOrderTime()).orderNumber(orders.getNumber()).orderAmount(orders.getAmount()).build();return orderSubmitVO;}
}
- 主要逻辑:
- 处理各种业务异常(地址簿为空, 购物车数据为空)
- 向订单表插入1条数据
- 向订单明细表插入n条数据
- 清空当前用户的购物车数据
- 封装VO返回数据
- 虽然前端对异常情况进行了限制, 但是为了代码的健壮性, 后端还是要进行判断
- 通过事务注解进行事务管理
Maaper: 新建OrderMapper(操作订单表) 和 OrderDetailMapper(操作订单明细表)
@Mapper
public interface OrderMapper {/*** 插入订单数据* @param orders*/void insert(Orders orders);
}
<mapper namespace="com.sky.mapper.OrderMapper"><insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into orders (number, status, user_id, address_book_id, order_time, checkout_time, pay_method, pay_status,amount, remark, phone, address, consignee, estimated_delivery_time, delivery_status,pack_amount, tableware_number, tableware_status)values (#{number}, #{status}, #{userId}, #{addressBookId}, #{orderTime}, #{checkoutTime}, #{payMethod},#{payStatus}, #{amount}, #{remark}, #{phone},#{address}, #{consignee}, #{estimatedDeliveryTime}, #{deliveryStatus}, #{packAmount},#{tablewareNumber}, #{tablewareStatus})</insert></mapper>
@Mapper
public interface OrderDetailMapper {/*** 批量插入订单明细数据* @param orderDetailList*/void insertBatch(ArrayList<OrderDetail> orderDetailList);
}
<mapper namespace="com.sky.mapper.OrderDetailMapper"><insert id="insertBatch">insert into order_detail (name,image,order_id,dish_id,setmeal_id,dish_flavor,number,amount)values<foreach collection="orderDetailList" item="od" separator=",">(#{od.name},#{od.image},#{od.orderId},#{od.dishId},#{od.setmealId},#{od.dishFlavor},#{od.number},#{od.amount})</foreach></insert>
</mapper>
测试
订单支付
介绍
资质限制: 需要使用营业执照申请商户号, 个人账户无法开通支付权限, 有了商户号可以开通支付资质
微信支付的接入流程: 在商户平台把商户号与AppId绑定后, 就可以使用appid和商户号进行支付
微信小程序支付时序图
重要步骤
- 后端调用微信官方的预支付接口,拿到预支付订单,返给前端
- 前端根据预支付订单发起微信支付
- 微信服务器把支付结果返给后端, 后端更新支付状态
预支付交易订单(JSAPI下单): 商户系统调用该接口在微信支付服务后台生成预支付交易单, 目的是获取预支付交易会话标识
调起支付: 使用微信支付提供的小程序方法调起小程序支付, 传入预支付交易会话标识(prepay_id), 完成支付
准备工作
为了保证支付的安全, 在支付过程中要对数据加密/解密/签名, 就要使用到微信支付平台证书/商户私钥文件, 这两个文件要在商户管理后台获取
支付成功后, 微信服务器要通过我们配置的域名, 回调我们的系统程序, 通知我们用户支付的结果
内网穿透:
- 我们的程序处于开发阶段, 没有部署到公网上, 只在本地的局域网运行
- 所以需要使用内网穿透技术, 获得一个临时公网ip, 供微信后台回调
软件安装: 通过官网下载, 或者使用资料中的安装包, wind双击安装即可
验证: 登录官网 cpolar - secure introspectable tunnels to localhost, 选择验证菜单, 复制 Authtoken 值
生成配置文件: 在软件安装目录, 打开cmd窗口, 执行命令 cpolar.exe authtoken XXXXXXXXX值
- 生成配置文件, 只需执行一次就行
启动服务: 软件安装目录, 打开cmd窗口, 执行命令 cpolar.exe http 8080
- 苍穹外卖后端服务运行在8080端口, 所以在命令中要指定映射端口是8080
- 访问测试: https://1ded711e.r27.cpolar.top/doc.html#/home
代码导入
配置微信支付相关的参数
准备配置属性封装类, 用来封装配置文件中的数据, 使用时注入该类, 就可以获取配置文件中的数据
@Component
@ConfigurationProperties(prefix = "sky.wechat")
@Data
public class WeChatProperties {private String appid; //小程序的appidprivate String secret; //小程序的秘钥private String mchid; //商户号private String mchSerialNo; //商户API证书的证书序列号private String privateKeyFilePath; //商户私钥文件private String apiV3Key; //证书解密的密钥private String weChatPayCertFilePath; //平台证书private String notifyUrl; //支付成功的回调地址private String refundNotifyUrl; //退款成功的回调地址}
导入代码: 新增nofity包存放PayNotifyController.java文件, 处理微信后台的消息, 其他代码按需复制不要覆盖
阅读代码
用户进行订单支付, 首先访问OrderController的支付接口, 前端要把订单号传递过来, 我们使用DTO接收
@RestController("userOrderController")
@RequestMapping("/user/order")
@Slf4j
@Api(tags = "用户端订单相关接口")
public class OrderController {@Autowiredprivate OrderService orderService;/*** 订单支付** @param ordersPaymentDTO* @return*/@PutMapping("/payment")@ApiOperation("订单支付")public Result<OrderPaymentVO> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {log.info("订单支付:{}", ordersPaymentDTO);OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);log.info("生成预支付交易单:{}", orderPaymentVO);return Result.success(orderPaymentVO);}}
再调用orderService中payment方法, 处理支付逻辑
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate WeChatPayUtil weChatPayUtil;@Value("${sky.shop.address}")private String shopAddress;@Value("${sky.baidu.ak}")private String ak;/*** 订单支付** @param ordersPaymentDTO* @return*/public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {// 当前登录用户idLong userId = BaseContext.getCurrentId();User user = userMapper.getById(userId);//调用微信支付接口,生成预支付交易单JSONObject jsonObject = weChatPayUtil.pay(ordersPaymentDTO.getOrderNumber(), //商户订单号new BigDecimal(0.01), //支付金额,单位 元"苍穹外卖订单", //商品描述user.getOpenid() //微信用户的openid);if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {throw new OrderBusinessException("该订单已支付");}OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);vo.setPackageStr(jsonObject.getString("package"));return vo;}}
- 通过weChatPayUtil工具类中的pay方法完成订单支付
- pay方法首先调用 jsapi方法完成订单数据的封装
- 再调用 post方法请求微信后台的下单接口, 完成下单操作, 并获取预支付交易标识
- 对支付数据进行一系列的封装, 加密和签名, 最终得的一个JSON对象, 供前端用于调起微信支付
- 把JSON数据在封装到VO对象中, 返回给前端
前端微信支付完成后, 微信后台服务器会回调我们的系统, 通知用户支付的结果, 我们使用controller接收并处理
/*** 支付回调相关接口*/
@RestController
@RequestMapping("/notify")
@Slf4j
public class PayNotifyController {@Autowiredprivate OrderService orderService;@Autowiredprivate WeChatProperties weChatProperties;/*** 支付成功回调** @param request*/@RequestMapping("/paySuccess")public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {//读取数据String body = readData(request);log.info("支付成功回调:{}", body);//数据解密String plainText = decryptData(body);log.info("解密后的文本:{}", plainText);JSONObject jsonObject = JSON.parseObject(plainText);String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号String transactionId = jsonObject.getString("transaction_id");//微信支付交易号log.info("商户平台订单号:{}", outTradeNo);log.info("微信支付交易号:{}", transactionId);//业务处理,修改订单状态、来单提醒orderService.paySuccess(outTradeNo);//给微信响应responseToWeixin(response);}/*** 读取数据** @param request* @return* @throws Exception*/private String readData(HttpServletRequest request) throws Exception {BufferedReader reader = request.getReader();StringBuilder result = new StringBuilder();String line = null;while ((line = reader.readLine()) != null) {if (result.length() > 0) {result.append("\n");}result.append(line);}return result.toString();}/*** 数据解密** @param body* @return* @throws Exception*/private String decryptData(String body) throws Exception {JSONObject resultObject = JSON.parseObject(body);JSONObject resource = resultObject.getJSONObject("resource");String ciphertext = resource.getString("ciphertext");String nonce = resource.getString("nonce");String associatedData = resource.getString("associated_data");AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));//密文解密String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciphertext);return plainText;}/*** 给微信响应** @param response*/private void responseToWeixin(HttpServletResponse response) throws Exception {response.setStatus(200);HashMap<Object, Object> map = new HashMap<>();map.put("code", "SUCCESS");map.put("message", "SUCCESS");response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));response.flushBuffer();}}
- 用户支付成功, 微信后台会返回给我们用户支付的数据, 我们进行数据读取和解密
- 再调用orderService的paySuccess方法, 修改该订单的状态为成功
- 订单状态修改完成后, 我们要带给微信后台一个响应, 否则微信后台会一直通知我们
修改订单状态
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderMapper orderMapper;/*** 支付成功,修改订单状态** @param outTradeNo*/public void paySuccess(String outTradeNo) {// 根据订单号查询订单Orders ordersDB = orderMapper.getByNumber(outTradeNo);// 根据订单id更新订单的状态、支付方式、支付状态、结账时间Orders orders = Orders.builder().id(ordersDB.getId()).status(Orders.TO_BE_CONFIRMED).payStatus(Orders.PAID).checkoutTime(LocalDateTime.now()).build();orderMapper.update(orders);}}
@Mapper
public interface OrderMapper {/*** 根据订单号查询订单** @param orderNumber*/@Select("select * from orders where number = #{orderNumber}")Orders getByNumber(String orderNumber);/*** 修改订单信息** @param orders*/void update(Orders orders);
}
<?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.sky.mapper.OrderMapper"><update id="update" parameterType="com.sky.entity.Orders">update orders<set><if test="cancelReason != null and cancelReason!='' ">cancel_reason=#{cancelReason},</if><if test="rejectionReason != null and rejectionReason!='' ">rejection_reason=#{rejectionReason},</if><if test="cancelTime != null">cancel_time=#{cancelTime},</if><if test="payStatus != null">pay_status=#{payStatus},</if><if test="payMethod != null">pay_method=#{payMethod},</if><if test="checkoutTime != null">checkout_time=#{checkoutTime},</if><if test="status != null">status = #{status},</if><if test="deliveryTime != null">delivery_time = #{deliveryTime}</if></set>where id = #{id}</update></mapper>
功能测试
相关文章:

[苍穹外卖]-08微信支付详解
地址簿管理 分析需求: 查询地址列表/新增地址/修改地址/删除地址/设置默认地址/查询默认地址 接口设计 新增地址接口 查询用户所有的地址接口 查询默认地址接口 根据id修改地址接口 根据id删除地址接口 根据id查询地址接口 设置默认地址接口 数据库设计: 收货地址簿(address_…...

教你五句在酒桌上和领导说的话语
1、今天很荣幸能和领导一起吃饭,我敬领导一杯希望领导工作顺利身体健康!生意兴隆!2、我敬领导一杯感谢领导平时对我的关照先干为敬!3、谢谢领导这次给我这个机会我一定会好好把握的请领导放心我一定会好好工作绝对不辜负领导对我的期望4.领导能来这里我们感到非常骄…...

景联文科技:专业图像采集服务,助力智能图像分析
景联文科技是专业数据服务公司,致力于为人工智能企业提供从数据采集、清洗到标注的全流程解决方案。协助客户解决AI开发过程中数据处理环节的关键问题,助力企业实现智能化转型。 1.多样化的图像采集服务 景联文科技提供多样化的图像采集服务,…...
QT QTcpSocket作为客户端
前言 QTcpSocket是Qt提供的关于TCP网络通信的类。QTcpSocket是一个异步的类,能够非阻塞式发送和接收数据。QTcpSocket内部封装了网络通信相关细节,对外提供便利的接口去帮助开发人员实现简历连接、断开连接、数据收发。 主要内容 基本使用方式 项目文…...
【系统架构设计师-2023年】综合知识-答案及详解
更多内容请见: 备考系统架构设计师-核心总结索引 文章目录 【第1~2题】【第3题】【第4~5题】【第6题】【第7题】【第8题】【第9题】【第10~11题】【第12题】【第13题】【第14题】【第15题】【第16题】【第17题】【第18题】【第19题】【第20题】【第21~22题】【第23题】【第24~…...

树莓派3B点灯(1)-- 四种方法
先做个简单一丢丢的吧。。。正好最近工作也要用这个。这次直接给够四种方法,给好给满。分别是Python点,用户空间配置GPIO点,设备树配置内核Leds驱动点,自己写驱动点。 用的板子是树莓派3B,GPIO 26口,蓝光L…...

Android解析XML格式数据
文章目录 Android解析XML格式数据搭建Web服务器Pull解析方式SAX解析方式 Android解析XML格式数据 通常情况下,每个需要访问网络的应用程序都会有一个自己的服务器,我们可以向服务器提交数据,也可以从服务器上获取数据。不过这个时候就出现了…...
数学建模笔记—— 灰色关联分析[GRA]
数学建模笔记—— 灰色关联分析[GRA] 灰色关联分析(GRA)1. 相关概念1.1 灰色系统1.2 什么是关联分析?1.3 灰色关联分析 2. 关联分析步骤3. 典型例题3.1 关联分析例题3.2 灰色关联综合评价 4. python代码实现4.1 关联分析4.2 灰色关联综合评价 灰色关联分析(GRA) 1.…...

ICM20948 DMP代码详解(13)
接前一篇文章:ICM20948 DMP代码详解(12) 上一回完成了对inv_icm20948_set_chip_to_body_axis_quaternion函数第2步即inv_rotation_to_quaternion函数的解析。回到inv_icm20948_set_chip_to_body_axis_quaternion中来,继续往下进行…...
【论软件需求获取方法及其应用】
摘要 2023 年 3 月,我所在的公司承接了某油企智慧加油站平台的建设工作。该项目旨在帮助加油站提升运营效率、降低运营成本和提高销售额。我在该项目中担任系统架构设计师,负责整个项目的架构设计工作。 本文以该项目为例,详细论述软件需求获…...

使用ESP8266和OLED屏幕实现一个小型电脑性能监控
前言 最近大扫除,发现自己还有几个ESP8266MCU和一个0.96寸的oled小屏幕。又想起最近一直想要买一个屏幕作为性能监控,随机开始自己diy。 硬件: ESP8266 MUColed小屏幕杜邦线可以传输数据的数据线 环境 Windows系统Qt6Arduino Arduino 库…...

Nexpose v6.6.266 for Linux Windows - 漏洞扫描
Nexpose v6.6.266 for Linux & Windows - 漏洞扫描 Rapid7 Vulnerability Management, release Aug 21, 2024 请访问原文链接:https://sysin.org/blog/nexpose-6/,查看最新版。原创作品,转载请保留出处。 作者主页:sysin.o…...
ess6新特性
1、let、const 块级作用域声明变量和常量 2、箭头函数 不能构建函数 不能new 没.prototype属性 没有this指向 this指向是根据上下文的 往上层查找 没有arguments(参数) 3、模板字符串 ${} 字符串中嵌入表达式 4、解构赋值 5、Promise 处理异步操作的标准机制 6、for of 遍历…...

C语言蓝桥杯:语言基础
竞赛常用库函数 最值查询 min_element和max_element在vector(迭代器的使用) nth_element函数的使用 例题lanqiao OJ 497成绩分析 第一种用min_element和max_element函数的写法 第二种用min和max的写法 二分查找 二分查找只能对数组操作 binary_search函数,用于查找…...

axure之变量
一、设置我们的第一个变量 1、点击axure上方设置一个全局变量a 3 2、加入按钮、文本框元件点击按钮文档框展示变量值。 交互选择【单击时】【设置文本】再点击函数。 点击插入变量和函数直接选择刚刚定义的全局变量,也可以直接手动写入函数(注意写入格式。) 这…...
vue缓存用法
Store 临时缓存 特点:需要定义,有初始值、响应式、全局使用、刷新重置 Pinia官方文档 https://pinia.vuejs.org 创建 store 缓存 示例代码 import {defineStore} from pinia import {store} from //storeexport const useMyStore defineStore({// 定义…...

栈入门,括号匹配问题
利用栈这道题应该很轻松可以解决,下面给出常用的代码: public static boolean isValid(String s) {// 创建一个栈来保存左括号Stack<Character> stack new Stack<>();// 遍历字符串中的每个字符for (char c : s.toCharArray()) {// 如果是…...

Vue入门学习笔记-表单
可以使用v-model 指令在表单控件元素上创建双向数据绑定。 引言: Vue采用了MVVM(Model-View-ViewModel)架构模式,通过指令可以快速实现数据和视图的双向绑定 修改视图层时,模型层也会改变;修改模型层&#…...

TCP通信三次握手、四次挥手
目录 前言 一、三次握手 TCP三次握手的详细过程 二、四次挥手 四次挥手的详细过程 前言 前面我说到了,UDP通信的实现,但我们经常说UDP通信不可靠,是因为他只会接收和发送,并不会去验证对方收到没有,那么我们说TCP通…...

【实施文档】软件项目实施方案(Doc原件2024实际项目)
软件实施方案 二、 项目介绍 三、 项目实施 四、 项目实施计划 五、 人员培训 六、 项目验收 七、 售后服务 八、 项目保障措施软件开发管理全套资料包清单: 工作安排任务书,可行性分析报告,立项申请审批表,产品需求规格说明书&am…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...