学习大数据之JDBC(使用JAVA语句进行SQL操作)(3)

文章目录

  • DBUtils工具包
    • 准备工作
    • DBUtils的介绍
    • QueryRunner
      • 空参的QueryRunner的介绍以及使用
      • 有参QueryRunner的介绍以及使用
    • ResultSetHandler结果集
      • BeanHandler<T>
      • BeanListHandler<T>
      • ScalarHander
      • ColumnListHander
  • 事务
    • 事务
      • 事务_转账分析图
      • 实现转账(不加事务)
      • 事务的介绍
      • DButils实现转账(添加事务)
      • mysql中操作事务
      • 分层思想介绍以及框架搭建
        • 转账_表现层实现
        • 转账_服务层实现
        • 转账_持久层实现
    • ThreadLocal
      • ThreadLocal基本使用和原理
      • 连接池对象管理类(小秘书)
    • 事务的特性以及隔离级别
      • 事务特性ACID
      • 并发访问问题
      • 隔离级别:解决问题
      • 演示

DBUtils工具包

准备工作

在这里插入图片描述

DBUtils的介绍

1.概述:简化jdbc开发的工具
2.三大核心对象
QueryRunner 执行sql
ResultSetHandler:处理结果集
DBUtils类: 主要用于事务管理,关闭资源等

QueryRunner

空参的QueryRunner的介绍以及使用

1.构造:QuerryRunner()
2.特点:
连接对象需要我们自己维护,自己关闭
3.方法
int update(Connection conn, String sql, Object… params)-> 针对于增删改
conn:连接对象
sql:sql语句->支持?占位符
params:为?赋值->可变参数
第一个值自动匹配sql语句中的第一个?以此类推
query(Connection conn, String sql, ResultSetHandler rsh, Object… params)->针对查询

public class test04 {public static void main(String[] args) throws SQLException {QueryRunner queryRunner = new QueryRunner();Connection conn = DruidUtils.getConn();String sql = "insert into category(cname) values(?);";queryRunner.update(conn,sql,"服装");DruidUtils.close(conn,null,null);}
}

有参QueryRunner的介绍以及使用

1.构造:QueryRunner(DataSource ds)
2.特点:
Connection连接对象,不用我们自己管理,全部交由连接池管理
3.方法:
int update(String sql, Object… params)-> 针对于增删改
sql:sql语句->支持?占位符
params:为?赋值->可变参数
第一个值自动匹配sql语句中的第一个?以此类推
query(String sql, ResultSetHandler rsh, Object… params)->针对查询
代码示例

ResultSetHandler结果集

在这里插入图片描述

BeanHandler

1.作用:将查询出来的结果集中的第一行数据封装成javabean对象
2.方法
query(String sql, ResultSetHandler rsh, Object… params)>有参QueryRunner时使用
query(Connection conn, String sql, ResultSetHandler rsh, Object… params)->空参QueryRunner时使用
3.构造
BeanHandler(class type)
传递的是class对象其实就是我们想要封装的javabean类的class对象
将想查询出来的数据封装成哪个Javabean对象,就写哪个javabean的class对象
4.怎么理解
将查询出来的数据为javabean中的成员变量赋值

@Testpublic void beanHandler() throws SQLException {QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());Category category = queryRunner.query("select * from category",new BeanHandler<>(Category.class));System.out.println("category = " + category);}

注意在写之前需要创建一个javabean对象

public class Category {private int id;private String cname;public Category() {}public Category(int id, String cname) {this.id = id;this.cname = cname;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getCname() {return cname;}public void setCname(String cname) {this.cname = cname;}
}

BeanListHandler

1.作用 将查询出来的结果每一条数据都封装成一个个的javabean对象,将这些javabean对象放到list集合中
2.构造
BeanListHandler(Class type)
传递的是class对象其实就是我们想要封装的javabean类的class对象
想将查询出来的数据封装成哪个javabean对象,就写哪个javabean的class对象
3.怎么理解“
将查询出来的多条数据封装成多个javabean对象,将多个javabean对象放到list集合中

public void beanListHandler() throws SQLException {QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());List<Category> list = queryRunner.query("select * from category",new BeanListHandler<>(Category.class));for (Category category : list) {System.out.println(category.getCid());}}

ScalarHander

1.作用:主要是用于处理单值的查询结果,执行的select语句,结果集只有一个
2.构造:
ScalarHandler(int columnIndex)->不常用->指定第几列
ScalarHandler(String columnName)->不常用->指定列名
ScalarHandler()->常用
3.注意:
ScalarHander和聚合函数使用更有意义

 public void scalarhander() throws SQLException {QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());Object o1 = queryRunner.query("select * from category;", new ScalarHandler<>(2));Object o2 = queryRunner.query("select * from category;", new ScalarHandler<>("cname"));Object o3 = queryRunner.query("select * from category;", new ScalarHandler<>());System.out.println(o1);System.out.println(o2);System.out.println(o3);}

ColumnListHander

1.作用 将查询出来的结果中的某一列数据,存储到list集合中
2.构造:
ColumnListHandler(int columnIndex)->指定第几列
ColumnListHandler(String columnName)->指定列名
ColumnListHandler()-> 默认显示查询结果中的第一列数据
3.注意:
ColumnListHandler可以指定泛型类型,如果不指定,返回的list泛型就是object类型

 public void columnListHandler() throws SQLException {QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());List<Object> query = queryRunner.query("select * from category;", new ColumnListHandler<>(2));List<Object> query_1 = queryRunner.query("select * from category;", new ColumnListHandler<>());for (Object o : query) {System.out.println(o);}}

事务

事务

事务_转账分析图

在这里插入图片描述
建表

CREATE TABLE account(`name` VARCHAR(20),money INT
);

实现转账(不加事务)

public class test01 {public static void main(String[] args) {QueryRunner queryRunner = new QueryRunner();Connection conn = DruidUtils.getConn();String outMoney = "update account set money = money - 1000 where name = ?";String inputMoney = "update account set money = money + 1000 where name = ?";try {queryRunner.update(conn,outMoney,"王也");queryRunner.update(conn,inputMoney,"张楚岚");} catch (SQLException e) {e.printStackTrace();}finally {DruidUtils.close(conn,null,null);}}
}

事务的介绍

1.事务:是管理多条sql语句的一个形式,可以将多条sql看成是一组操作,是的这一组操作要么全成功,要么全失败,mysql自带事务,带式mysql的事务一次只能管理一条sql,我们需要关闭sql的自带事务,开启手动事务
2.事务操作:Connection中的方法
a.void setAutoCommit(boolean autoCommit) -> 开启手动事务
autoCommit:false -> 代表关闭mysql自带事务,开启手动事务
b.void commit()-> 提交事务 -> 如果事务一旦提交,数据将永久保存,不可逆
c.void rollback()->回滚事务-> 如果事务回滚,数据将还原到上一次改变前的数据
3.注意
如果想让事务管理多条sql语句,那么sql之间用的连接或者说事务操作作用的连接对象必须是同一条连接对象

DButils实现转账(添加事务)

相关代码块

public class test02 {public static void main(String[] args) throws SQLException {QueryRunner queryRunner = new QueryRunner();java.sql.Connection conn1 = null;try {conn1 = DruidUtils.getConn();System.out.println(conn1);//开启事务conn1.setAutoCommit(false);String outputmoney = "update account set money = money - 1000 where name = ?";String inputmoney = "update account set money = money + 1000 where name = ?";queryRunner.update(conn1, outputmoney, "王也");queryRunner.update(conn1, inputmoney, "张楚岚");//提交事务conn1.commit();System.out.println("转账成功");}catch (Exception e){//回滚事务try {conn1.rollback();System.out.println("转账失败");}catch (SQLException ex){ex.printStackTrace();}e.printStackTrace();}finally {//关闭资源DruidUtils.close(conn1,null,null);}}
}

mysql中操作事务

1.开启事务
begin
2.提交事务
commit
3.回滚事务
rollback

#开启事务
BEGIN;
update account set money = money - 1000 where name = "王也";
update account set money = money + 1000 where name = "张楚岚";
#提交事务
COMMIT;
#回滚事务
ROLLBACK;

分层思想介绍以及框架搭建

1.表现层(web层) 和页面打交道 ---- 接收请求,回响应
2.业务层(service层) 写业务的,做计算
3.持久层(dao层) 和数据库打交道—对数据库的数据进行增删改查
在这里插入图片描述
创建package ---- 公司域名倒着写,都是小写
在这里插入图片描述

转账_表现层实现
public class AccountController {public static void main(String[] args) {//创建scannerScanner scanner = new Scanner(System.in);System.out.println("请你输入要出钱的人");String outname = scanner.next();System.out.println("请你输入要收钱的人");String inname = scanner.next();System.out.println("请输入转账金额");int money = scanner.nextInt();//将输入的三个数据传递到service层AccountService accountService = new AccountService();accountService.transfer(outname,inname,money);}
}
转账_服务层实现
public class AccountService {public void transfer(String outname,String inname,int money){AccountDao accountDao = new AccountDao();Connection conn = DruidUtils.getConn();//开启事务try {conn.setAutoCommit(false);accountDao.outmoney(conn,outname,money);accountDao.inmoney(conn,inname,money);//提交事务conn.commit();System.out.println("转账成功");} catch (Exception e) {try {conn.rollback();System.out.println("转账失败");} catch (SQLException ex) {ex.printStackTrace();}e.printStackTrace();}finally {DruidUtils.close(conn,null,null);}}
}
转账_持久层实现
public class AccountDao {public void outmoney(Connection conn,String outname,int money) throws SQLException {QueryRunner queryRunner = new QueryRunner();String sql = "update account set money = money - ? where name = ?";queryRunner.update(conn,sql,money,outname);}public void inmoney(Connection conn,String inname,int money) throws SQLException {QueryRunner queryRunner = new QueryRunner();String sql = "update account set money = money + ? where name = ?";queryRunner.update(conn,sql,money,inname);}
}

1.为什么要分层写代码?
a.减低耦合度
b.每层只干自己的事儿,别的层的事儿我不干,好维护
2.考虑问题:
1.问题1:service层中干了从数据库连接池中获取连接对象,也干了事务操作;但是service不应该干这些事儿
service其实只干业务逻辑判断,业务处理
2.问题2:如果不在service层获取连接对象,那么怎么传递到dao层呢?又怎么保证service和dao层中的连接对象是同一个呢?如果不是同一个,事务管理也就废废了

3.问题解决:给service和dao层面试一个"小秘书",获取连接,事务操作,都让"小秘书"干
同时还要保证service和dao使用的连接是同一条连接

ThreadLocal

ThreadLocal基本使用和原理

1.ThreadLocal:线程管理类
2.创建:
ThreadLocal 名字 = new ThreadLocal()
3.方法:
set(数据) – 往ThreadLocal中存储数据
get() — 获取数据
4.特点
a.ThreadLocal中一次只能存储一个数据,后米的数据会把前面的数据覆盖掉
b.一个线程往ThreadLocal中存储的数据,其他线程获取不到
c.ThreadLocal底层操作了Map,key为当前正在执行的线程对象,value为我们存储的数据
当我一存,值就会和当前前程绑死,也就是说值指数已当前线程对象,其他线程对象拿不到
在这里插入图片描述

public class Demo01ThreadLocal {public static void main(String[] args) {ThreadLocal<String> threadLocal = new ThreadLocal<>();threadLocal.set("王也");System.out.println(threadLocal.get());}
}

连接池对象管理类(小秘书)

代码实现

public class ConnectionManager {private static  ThreadLocal<Connection> t1 = new ThreadLocal<Connection>();/** 从连接池中获取连接对象,放到ThreadLocal中* 然后从ThreadLocal中再拿出要使用的连接对象* */public static Connection getconn(){//先从ThreadLocal中获取连接对象Connection conn = t1.get();if (conn == null){//从连接池中获取连接对象conn = DruidUtils.getConn();t1.set(conn);}return conn;}//开启事务public static void begin(){try {getconn().setAutoCommit(false);} catch (SQLException e) {e.printStackTrace();}}//提交事务public static void commit(){try {getconn().commit();} catch (SQLException e) {e.printStackTrace();}}//回滚事务public static void back(){try {getconn().rollback();} catch (SQLException e) {e.printStackTrace();}}//关闭连接public static void close(){try {getconn().close();} catch (SQLException e) {e.printStackTrace();}}
}

事务的特性以及隔离级别

事务特性ACID

1.原子性(atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
2.一致性(Consistency)事务前后数据的完整性必须保持一致。
3.隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离,正常情况下数据库是做不到这一点的,可以设置隔离级别,但是效率会非常低。
4.持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

并发访问问题

如果不考虑隔离性,事务存在3中并发访问问题

  1. 脏读:一个事务读到了另一个事务未提交的数据.
  2. 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
  3. 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。

隔离级别:解决问题

数据库规定了四种隔离级别,分别用于描述两个事务并发的所用情况

  1. read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
    a)存在:3个问题(脏读、不可重复读、虚读)。
    b)解决:0个问题
  2. read committed 读已提交,一个事务读到另一个事务已经提交的数据。
    a)存在:2个问题(不可重复读、虚读)。
    b)解决:1个问题(脏读)
  3. repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
    a)存在:1个问题(虚读)。
    b)解决:2个问题(脏读、不可重复读)
    4.serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
    a)存在:0个问题。
    b)解决:3个问题(脏读、不可重复读、虚读)
    安全和性能对比
    安全性:serializable > repeatable read > read committed > read uncommitted
    性能 : serializable < repeatable read < read committed < read uncommitted
    常见数据库的默认隔离级别:
  • MySql:repeatable read
  • Oracle:read committed

演示

查询数据库的隔离级别

show variables like '%isolation%';
select @@tx_isolation;

设置数据库的隔离级别

  • set session transactionisolation level 级别字符串

  • 级别字符串:readuncommittedread committedrepeatable readserializable

  • 例如:set session transaction isolation level read uncommitted;
    读未提交:readuncommitted

  • A窗口设置隔离级别

    • AB同时开始事务
    • A 查询
    • B 更新,但不提交
    • A 再查询?-- 查询到了未提交的数据
    • B 回滚
    • A 再查询?-- 查询到事务开始前数据
  • 读已提交:read committed

    • A窗口设置隔离级别
      • AB同时开启事务
      • A查询
      • B更新、但不提交
      • A再查询?–数据不变,解决问题【脏读】
      • B提交
      • A再查询?–数据改变,存在问题【不可重复读】
  • 可重复读:repeatable read

    • A窗口设置隔离级别
      • AB 同时开启事务
      • A查询
      • B更新, 但不提交
      • A再查询?–数据不变,解决问题【脏读】
      • B提交
      • A再查询?–数据不变,解决问题【不可重复读】
      • A提交或回滚
      • A再查询?–数据改变,另一个事务
  • 串行化:serializable

    • A窗口设置隔离级别
    • AB同时开启事务
    • A查询
      • B更新?–等待(如果A没有进一步操作,B将等待超时)
      • A回滚
      • B 窗口?–等待结束,可以进行操作
        总结:
        我们最理想的状态是:一个事务和其他事务互不影响
        但是如果不考虑隔离级别的话,就会出现多个事务之间互相影响
        而事务互相影响的表现方式为:
        脏读
        不可重复读
        虚读/幻读

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

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

相关文章

搜索与图论——拓扑排序

有向图的拓扑排序就是图的宽度优先遍历的一个应用 有向无环图一定存在拓扑序列&#xff08;有向无环图又被称为拓扑图&#xff09;&#xff0c;有向有环图一定不存在拓扑序列。无向图没有拓扑序列。 拓扑序列&#xff1a;将一个图排成拓扑序后&#xff0c;所有的边都是从前指…

Redis的高可用(主从复制、哨兵模式、集群)的概述及部署

目录 一、Redis主从复制 1、Redis的主从复制的概念 2、Redis主从复制的作用 ①数据冗余&#xff1a; ②故障恢复&#xff1a; ③负载均衡&#xff1a; ④高可用基石&#xff1a; 3、Redis主从复制的流程 4、Redis主从复制的搭建 4.1、配置环境以及安装包 4.2所有主机…

智慧InSAR专题———模拟数据实现现实场景异常形变点识别(论文解读)

文章目录 &#xff08;近期想静下心回顾近期看的佳作&#xff0c;会写一下自己的总结&#xff0c;大家如果对此系列感兴趣&#xff0c;每周踢一下我&#xff0c;周更&#xff0c;持续更新&#xff09;0 前言1 Automated deformation detection and interpretation using InSAR …

蓝桥杯练习系统(算法训练)ALGO-959 P0705 集合运算

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 输入两个整数集合A、B&#xff0c;求出他们的交集、并集以及B在A中的余集。交集、并集和余集的计算都要求写成一个单独的函数。   输…

分类预测 | Matlab实现DRN深度残差网络数据分类预测

分类预测 | Matlab实现DRN深度残差网络数据分类预测 目录 分类预测 | Matlab实现DRN深度残差网络数据分类预测分类效果基本介绍程序设计参考资料 分类效果 基本介绍 1.Matlab实现DRN深度残差网络数据分类预测&#xff08;完整源码和数据&#xff09;&#xff0c;运行环境为Matl…

Java spring 01 (图灵)

01.依赖注入 这里两个方法用到了datasource方法&#xff0c;不是bean这样的使用&#xff0c;没有autowird 会创建两个datasource configuration 会运行代理模式 会产生一个AppConfig的代理对象 这个代理对象会在spring的容器先找bean&#xff0c;datasource此时已经创建了be…

【前沿模型解析】潜在扩散模型 2-1 | 手撕感知图像压缩 基础块ResNet块

文章目录 1 残差结构回顾2 LDM结构中的残差结构设计2.1 组归一化GroupNorm层2.2 激活函数层2.3 卷积层2.4 dropout层 3 代码实现 1 残差结构回顾 残差结构应该是非常重要的基础块之一了&#xff0c;你肯定会在各种各样的网络模型结构里看到残差结构&#xff0c;他是非常强大的…

Python实现【坦克大战】+源码分享

写在前面&#xff1a; 坦克大战&#xff0c;这款经典的电子游戏&#xff0c;无疑是许多80后和90后心中不可磨灭的童年记忆。它不仅仅是一款游戏&#xff0c;更是那个时代科技娱乐方式的缩影&#xff0c;见证了电子游戏行业的起步与发展。 在那个电脑和网络尚未完全普及的年代…

【Linux】进程控制详解

目录 前言 进程创建 认识fork 写时拷贝 再谈fork 进程终止 进程退出码 用代码来终止进程 常见的进程终止的方式 exit _exit 进程等待 进程等待的必要性 进程等待的方式 wait waitpid 详解status参数 详解option参数 前言 本文适合有一点基础的人看的&#…

ruoyi-vue-pro 前端vue js直接import导入本地文件使用方法

我的xml文件名称叫w2101.xml 第一步&#xff0c;删除所有依赖&#xff0c;否则配置以后就会启动报错&#xff1a; 第二步配置对应的文件格式&#xff0c;我当前使用的是xml文件 config.module.rule(xml).test(/\.xml$/).use(xml-loader).loader(xml-loader).end();第三步…

python开发poc2

#本课知识点和目的&#xff1a; ---协议模块使用&#xff0c;Request 爬虫技术&#xff0c;简易多线程技术&#xff0c;编码技术&#xff0c;Bypass 后门技术 下载ftp服务器模拟器 https://lcba.lanzouy.com/iAMePxl378h 随便创建一个账户&#xff0c;然后登录进去把ip改成…

vue想要突破全局样式限制又不影响别的页面样式怎么办

<!-- 用scope盖不住全局&#xff0c;随意来个class匹配私定&#xff0c;搜索关键词&#xff1a;不要随便改&#xff0c;乱打class名 --> <style> .lkajsdfjkalsfhkljashkflhaskl .el-input.el-input--default.el-input--suffix { width: 160px !important; } …

cJSON(API的详细使用教程)

我们今天来学习一般嵌入式的必备库&#xff0c;JSON库 1&#xff0c;json和cJSON 那什么是JSON什么是cJSON&#xff0c;他们之间有什么样的关联呢&#xff0c;让我们一起来探究一下吧。 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&…

部署k8s客户端,及docker私仓部署

1.部署一个docker私仓 mkdir /opt/docker/registry #配置仓库密码 mkdir /opt/docker/auth cd /opt/docker/auth htpasswd -Bbn admin admin > htpasswd#运行docker私仓服务&#xff0c;下面端口5000:5000 前面的5000对应本机端口可以自定义 docker run -itd \ -v /opt/d…

LeetCode-33. 搜索旋转排序数组【数组 二分查找】

LeetCode-33. 搜索旋转排序数组【数组 二分查找】 题目描述&#xff1a;解题思路一&#xff1a;二分查找。1.找哨兵节点&#xff08;nums[0]或nums[-1]&#xff09;可以确定nums[mid]位于前一段或后一段有序数组中。2. 就是边界left和right的变换&#xff0c;具体看代码。解题思…

第十二届蓝桥杯大赛软件赛省赛C/C++大学B组

第十二届蓝桥杯大赛软件赛省赛C/C 大学 B 组 文章目录 第十二届蓝桥杯大赛软件赛省赛C/C 大学 B 组1、空间2、卡片3、直线4、货物摆放5、路径6、时间显示7、砝码称重8、杨辉三角形9、双向排序10、括号序列 1、空间 1MB 1024KB 1KB 1024byte 1byte8bit // cout<<"2…

Java设计模式:桥接模式实现灵活组合,超越单一继承的设计之道(十)

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 一、引言二、什么是桥接设计模式三、桥接设计模式的核心思想四、桥接设计模式的角色五、桥接设计模式的工作流程和实现实现方…

【JAVASE】带你了解instanceof和equals的魅力

✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;再无B&#xff5e;U&#xff5e;G-CSDN博客 1.instanceof instanceof 是 Java 的保留关键字。它的作用是测试…

MySQL复制拓扑1

文章目录 主要内容一.安装MySQL服务器1.MySQL 安装程序和其它文件保存在下发的 mysql8-files.iso 镜像文件中&#xff0c;可以使用虚拟光驱来提取到 Linux 文件系统。代码如下&#xff08;示例&#xff09;: 2.将 MySQL8.0 程序解压到 /opt 目录&#xff0c;再创建到 MySQL 默认…

NIUSHOP完美运营版商城 虚拟商品全功能商城 全能商城小程序 智慧商城系统 全品类百货商城

完美运营版商城/拼团/团购/秒杀/积分/砍价/实物商品/虚拟商品等全功能商城 干干净净 没有一丝多余收据 还没过手其他站 还没乱七八走的广告和后门 后台可以自由拖曳修改前端UI页面 还支持虚拟商品自动发货等功能 挺不错的一套源码 前端UNIAPP 后端PHP 一键部署版本 源码免费…