.net 软件开发模式——三层架构

这篇具有很好参考价值的文章主要介绍了.net 软件开发模式——三层架构。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

三层架构是一种常用的软件开发架构模式,它将应用程序分为三个层次:表示层、业务逻辑层和数据访问层。每一层都有明确的职责和功能,分别负责用户交互、业务处理和数据存储等任务。这种架构模式的优点包括易于维护和扩展、更好的组织结构和代码重用性、更高的安全性等方面。在具体实现中,需要根据不同的应用程序需求和技术平台选择适当的工具和框架,以达到最佳的开发效果和应用程序质量。

.net 软件开发模式——三层架构

 

在 .NET 中,我们同样可以使用三层架构来构建我们的应用程序,实现表示层、业务逻辑层和数据访问层的分离。下面分别介绍在 .NET 中如何实现这三层架构。

        1.表示层

在 .NET 中,我们可以使用 WinForms、WPF、ASP.NET 等技术来实现表示层的功能。在 WinForms 和 WPF 中,我们可以使用各种控件来构建用户界面,以显示和收集数据。在 ASP.NET 中,我们可以使用 ASP.NET Web 窗体、MVC、Web API 等技术来生成和管理 Web 页面。

在所有情况下,表示层的主要职责是接收用户输入和显示数据。例如,在 WinForms 中,我们可以使用窗体和控件来表示 UI,并通过事件处理程序和数据绑定来响应用户输入和显示数据。在 ASP.NET 中,我们可以使用代码片段来生成 HTML、CSS 和 JavaScript,并使用服务器端脚本和控件来处理事件和数据绑定。

        2.业务逻辑层

在 .NET 中,我们可以使用类和接口来实现业务逻辑层。这些类和接口通常包括与应用程序相关的业务逻辑和规则,例如验证、授权、计算和处理数据。

业务逻辑层通常向表示层公开 API,以便 UI 可以与业务逻辑进行交互。例如,在 ASP.NET MVC 中,我们可以使用控制器和模型来处理请求,并使用视图来处理 UI。在 WinForms 中,我们通常将业务逻辑封装在一个专用的类或类库中,并使用事件处理程序和数据绑定来处理 UI。

        3.数据访问层

在 .NET 中,我们可以使用 ADO.NET 和 Entity Framework 来访问数据。ADO.NET 提供了一组用于连接到和操作关系型数据库的类和接口,而 Entity Framework 则提供了一种对象关系映射(ORM)框架,使我们可以将对象与数据库记录进行映射和交互。

数据访问层通常会使用一些技术和工具来管理和维护数据,例如 SQL Server Management Studio 或 LINQ。我们可以使用 ADO.NET 和 Entity Framework 来执行数据库操作,并使用仓储模式或 DAL 等设计模式来创建底层数据库连接和访问代码。

 下面通过一个简易的例题来演示三层架构的简易模式:

  1. 首先建立一个数据库(EmployeeSystem)
  2. 内部包含两个表(AdminInfo),(EmployeeInfo)
  3. AdminInfo表(登录验证表)包含两字段:主键:Account(varchar)用户名,Pwd(varchar) 密码  用于登录验证
  4. EmployeeInfo表(学生信息表)包含六个字段 :主键自增列:Id(int)编号,EmployeeName (varchar)  姓名,EmployeeNo(varchar)学号,EmployeeAge(int )年龄,EmployeeSalary(int)就业工资,EmployeeJob(varchar)职业名称

  以上完成了示例数据库的搭建 

接下来打开Microsoft Visual Studio2022,开始新建项目

.net 软件开发模式——三层架构

 

  1.  在解决方案里创建一个Windows窗体应用程序取名UI作为我们这个项目的表示层
  2. 在解决方案里添加三个类库
  3. Model 模型层主要包含一些数据库中表对应的实体类,作为数据传递的载体
  4. DAL  数据访问层 :与数据库进行交互 执行增删改查的操作
  5. BLL 业务逻辑层:对业务逻辑进行处理 ,比如进行判断(必要的非空判断,添加前是否已经存在同样的数据)

 .net 软件开发模式——三层架构

        6.打开UI找到引用:

.net 软件开发模式——三层架构

 第2步:定义连接字符串

.net 软件开发模式——三层架构

        name 属性是用于标识连接字符串的名称的字符串。

        connectionString 是数据库连接时用到的字符串。

         providerName 属性是用于指定数据提供程序的名称字符串。它通常与 connectionString 属性一起使用,来告诉应用程序要使用哪个特定的 ADO.NET 数据提供程序来访问数据库。

最后再到UI层

.net 软件开发模式——三层架构

.net 软件开发模式——三层架构 

 到这里了接下来进入Model层

 我们首先从模型层开始写:

.net 软件开发模式——三层架构

这里类里添加了两个类分别对应数据库里的两张表以及各字段 

到这个模型层算搭建完(记得把修饰符改成公有的)

 下一步给各层设置相互之间的引用

.net 软件开发模式——三层架构

  1.  UI表示层需要引用BLL业务逻辑层和Model模型层
  2. DAL数据访问层需要引入Model模型层
  3. BLL业务逻辑层需要引入DAL数据访问层和Model模型层

 到这个项目的引用关系也就完事了

接下来就可以开始着手DAL数据访问层的内容了

DAL 层中的 SqlHelper 方法通常被称为数据访问助手方法(Data Access Helper Method),它们主要用于将应用程序的业务逻辑和底层数据访问逻辑分离开来。

通常情况下,SqlHelper 方法都包装了底层 ADO.NET API,以提供更简单、更易用的 API 接口,从而帮助开发人员更轻松地访问数据库。这些方法通常包括执行 SQL 语句、存储过程的方法,还有获取数据表、数据集等返回值的方法。

SqlHelper 方法可以接受连接字符串、SQL 语句、存储过程名称以及参数等信息作为参数,并使用它们来构建和执行数据库操作。通过将这些详细信息隐藏在方法内部,SqlHelper 方法帮助程序员简化了代码逻辑,提高了代码结构的清晰度和可读性。同时,SqlHelper 方法还可以处理数据库连接、异常处理等底层逻辑,从而提高了数据访问代码的可靠性和稳定性。

需要注意的是,不同的数据访问技术和开发框架通常有不同的 SqlHelper 实现方式,例如在 ASP.NET 中,我们通常使用 SqlHelper 类来访问 SQL Server 数据库,而在 Entity Framework 中,我们可以使用 DbContext 来管理数据访问逻辑,或者使用 LINQ 表达式来查询数据。

  1.  建立一个公有,静态类SqlHelper (内部方法也均为公有静态方法)
static readonly string ido= ConfigurationManager.ConnectionStrings["配置文件内add里的name名"].ToString();
//这段代码的作用是从应用程序配置文件 (App.config) 中获取名为 “配置文件内add里的name名” 的连接字符串,并将其存储在一个名为 “ido” 的静态只读字符串常量 ido 中。

     2.接下来写下两个方法

        思路:根据执行数据库增删改查操作的返回值来分类方法

                        1.查询操作返回的是DataTable 是一个 C# 中的数据表格类,它代表了一个内存中的数据表格,提供了一种方便的方法来存储和操作数据。

                        2.增删改操作的不同点只是sql语句有差异,他们共同点的返回类型都是受影响的行数。

                        3.由此存在了两种返回类型的值,可以在拟定两个返回值类型的方法分别为int和DataTable

   3.查询表值的方法返回DAtaTable

public static DataTable selectall(string sql)
        {
            //建立数据库连接 App.config里准备的数据库连接语句在这个时候可以体现用处了
            SqlConnection conn = new SqlConnection(ido);
            //打开数据库连接
            conn.Open();
            // 创建数据适配器对象
            SqlDataAdapter ter = new SqlDataAdapter(sql, conn);
            //实例化DataTable
            DataTable dt = new DataTable();
            // 使用数据适配器填充数据集
            ter.Fill(dt);
            //关闭数据库连接
            conn.Close();
            //返回值
            return dt;
        }

   4.增删改的方法返回受影响行数

public static int CUD(string sql)
        {
            SqlConnection conn = new SqlConnection(ido);
            conn.Open();
            //创建sql命令对象
            SqlCommand com= new SqlCommand(sql,conn);
            //用一个int 变量来接收受影响的返回行数
            int r=com.ExecuteNonQuery();
            //返回r的值
            return r;
        }

   5.参数化命令的增删改方法

public static bool selectal(string sql,List<SqlParameter> list)
        {
            
            SqlConnection conn = new SqlConnection(ido);
            conn.Open();
            SqlCommand com = new SqlCommand(sql, conn);
            //使用 AddRange 方法一次性添加多个 SqlParameter 对象,list需要转换成数组添加
            com.Parameters.AddRange(list.ToArray());
            //接收执行查询时返回的结果集中第一行第一列的值如果有数据就会大于0
            int r = Convert.ToInt32(com.ExecuteScalar());
            //判断r的值来决定返回的bool
            if(r >0) { return true; } else { return false; }
        }

到这一步数据访问助手的类已经完成了

DAL 数据访问层再为每张表定义一个类调用数据访问助手(在这里可以写入你需要执行的sql语句操作)

首先我们来做最简单的AdminInfo表(登录验证表)做执行一个登录的查询操作

        这里需要主要预防sql注入攻击,所以要采用参数化命令的方法

 在DAL层创建一个AdmDAL类调用SqlHelper类的方法

//参数为AdminInfo类
        public bool admopen(AdminInfo info)
        {
            //sql语句
            string sql = "select count(*) from AdminInfo where Account=@Account and Pwd=@Pwd";
            //创建了一个 List<SqlParameter> 实例,用于存储多个 SqlParameter 对象。然后通过调用 List<T> 对象的 Add 方法向集合添加一个参数,或使用 AddRange 方法一次添加多个参数
            List<SqlParameter> list= new List<SqlParameter>();
            list.Add(new SqlParameter("@Account", info.Account));
            list.Add(new SqlParameter("@Pwd", info.Pwd));
            //返回值调用SqlHelper的方法(传入sql语句和参数化命令对象的值)最终返回一个bool类型的值
            return SqlHelper.selectal(sql, list);
            

        }

DAL层最后一个类EmpADL类 EmployeeInfo表(学生信息表)的各项操作

全部采取的sqlHelper的方法

简易的只需要写一条sql语句再返回调用方法(传入string sql语句参数)

有参数化命令的方法就根据sql语句创建一个list集合来存储方法类的值再返回参数化命令方法(sql语句,list集合)

 //添加学生的方法
        public int EmpInsert(EmployeeInfo info)
        {
            string sql = "insert into EmployeeInfo values(@EmployeeName,@EmployeeNo,@EmployeeAge,@EmployeeSalary,@EmployeeJob)";
            List<SqlParameter> list = new List<SqlParameter>();
            list.Add(new SqlParameter("@EmployeeName", info.EmployeeName));
            list.Add(new SqlParameter("@EmployeeNo", info.EmployeeNo));
            list.Add(new SqlParameter("@EmployeeAge", info.EmployeeAge));
            list.Add(new SqlParameter("@EmployeeSalary", info.EmployeeSalary));
            list.Add(new SqlParameter("@EmployeeJob", info.EmployeeJob));
            return SqlHelper.CUD(sql, list);
        }
        //修改方法
        public int EmpUpdate(EmployeeInfo info)
        {
            string sql = "update EmployeeInfo set EmployeeName=@EmployeeName,EmployeeNo=@EmployeeNo,EmployeeAge=@EmployeeAge,EmployeeSalary=@EmployeeSalary,EmployeeJob=@EmployeeJob where Id=@Id";
            List<SqlParameter> list = new List<SqlParameter>();
            list.Add(new SqlParameter("@EmployeeName", info.EmployeeName));
            list.Add(new SqlParameter("@EmployeeNo", info.EmployeeNo));
            list.Add(new SqlParameter("@EmployeeAge", info.EmployeeAge));
            list.Add(new SqlParameter("@EmployeeSalary", info.EmployeeSalary));
            list.Add(new SqlParameter("@EmployeeJob", info.EmployeeJob));
            list.Add(new SqlParameter("@Id", info.Id));
            return SqlHelper.CUD(sql, list);
        }
        //删除方法
        public int EmpDelete(int id)
        {
            string sql = $"delete from EmployeeInfo where Id={id}";
            return SqlHelper.CUD(sql);
        }
        //查询整表的方法
        public DataTable select()
        {
            string sql = "select * from EmployeeInfo";
            return SqlHelper.selectall(sql);
        }
        //根据编号查询的方法
        public DataTable selectID(int id) {
            string sql = $"select * from EmployeeInfo where Id={id}";
            return SqlHelper.selectall(sql);
        }
        //根据姓名模糊查询的方法
        public DataTable SelectName(string name) {

            string sql = $"select * from EmployeeInfo where EmployeeName like '%{name}%'";
            return SqlHelper.selectall(sql);
        }

以上这些就是DAL 数据访问层的基本方法 如果有额外的需求的话可以根据个人的要求结合所学的知识再去新建方法体

下面我们再来到BLL业务逻辑层

业务逻辑层需要做什么呢?

简单的来讲就是连接UI表示层和DAL数据访问层的桥梁,在这里会对数据进行一些加工例如判断数据是否为空等等,该怎么做需要屏幕前的你来发挥了

下面来看示例

 public class EmpBLL
    {
        //实例化DAL数据访问层的EmpDAL类 并用方法调用EMpDAL类的方法
        private EmpDAL EmpDAL = new EmpDAL();
        //添加的方法
        public int EmpInsert(EmployeeInfo info)
        {
            return EmpDAL.EmpInsert(info);
        }
        //修改的方法
        public int EmpUpdate(EmployeeInfo info)
        {
            return EmpDAL.EmpUpdate(info);
        }
        //删除的方法
        public int EmpDelete(int id)
        {
            return EmpDAL.EmpDelete(id);
        }
        //查询整表的方法
        public DataTable select()
        {
            return EmpDAL.select();
        }
        //根据姓名模糊查询的方法
        public DataTable SelectName(string name)
        {
            return EmpDAL.SelectName(name);
        }
        //根据编号查询的方法
        public DataTable SelectID(int id)
        {
            return EmpDAL.selectID(id);
        }

以上是EmployeeInfo学生信息表需要在表示层使用的方法

接下来是AdminInfo登录信息表需要的业务逻辑层方法

 public class AdmBLL
    {
        //实例化数据访问层的AdmDAL类 并调用方法
        private AdmDAL AdmDAL=new AdmDAL();
        //登录方法
        public bool admopen(AdminInfo info)
        {
            return AdmDAL.admopen(info);
        }

    }

到这里业务逻辑层也就完工了

最后一个UI 表示层 使用WinForms窗体应用程序搭建

首先完成一个简易的登录操作

在UI层建立一个窗体form1 

使用控件 label* 2 ,textbox*2 button*1

.net 软件开发模式——三层架构

 给窗体登录按钮 双击添加点击事件

private void button1_Click(object sender, EventArgs e)
        {
            //实例化模型层Model 的AdminInfo类 并把文本框的值赋给里面的字段
            AdminInfo info= new AdminInfo();
            info.Account = textBox1.Text;
            info.Pwd= textBox2.Text;
            //实例化BLL 业务逻辑层调用相对应的方法
            AdmBLL adm= new AdmBLL();
            //定义一个bool值的变量来接收返回值
            bool f= adm.admopen(info);
            //通过判断f的值来选择执行的操作,为true则弹窗登录成功并打开后续的窗体
            if (f==true)
            {
                MessageBox.Show("登录成功");
                Form2 form2 = new Form2();
                form2.Show();
            }
            else { MessageBox.Show("登录失败"); }
        }

最后再给UI表示层添加一个form2窗体用于示例DAL和BLL的方法

为了简易采用dategridview

.net 软件开发模式——三层架构

 上面的窗体我们添加了多个按钮

先从窗体的加载事件开始,首先在窗体内部添加一个公有的BLL业务逻辑层的实例对象,便于调用其方法。

 public EmpBLL empBLL = new EmpBLL();//实例化业务逻辑层
//初始化dategridview的表值
//将方法放入form2_load窗体加载事件中即可初始化值
public void select()
        {
            dataGridView1.DataSource = empBLL.select();//调用业务逻辑层查询整表的方法加载到datagridview控件中
            dataGridView1.ClearSelection();//取消默认选中状态
        }

删除按钮:button2

//删除按钮点击事件 
private void button2_Click(object sender, EventArgs e)
        {
            //对datagridview的选中项进行判断,未选中行时弹窗提示
            if (dataGridView1.SelectedRows.Count<=0)
            {
                MessageBox.Show("请选中你需要删除的一行!");
            }
            else if (dataGridView1.SelectedRows.Count > 0)
            {
                //获取选中行的第一行第一列的值用 一个int的变量来接收方便调用后续的业务逻辑层删除的方法
                int id = Convert.ToInt32(this.dataGridView1.SelectedRows[0].Cells[0].Value);
                //接收删除方法的返回值进行受影响的行判断
                int i = empBLL.EmpDelete(id);
                if (i > 0)
                {
                    //删除成功时弹窗提示并调用给datagridview赋值的方法(select())
                    MessageBox.Show("删除成功");
                    select();
                }
                else { MessageBox.Show("删除失败"); }

            }
            
        }

添加的按钮:button1

private void button1_Click(object sender, EventArgs e)
        {
            //设置跳转到目标窗体并设置父子级
            Form3 form3 = new Form3();
            form3.Owner= this;
            form3.Show();
        }

form3窗体如下:

注:编号Id为自增列,所有只有五个字段

.net 软件开发模式——三层架构

 这里只需要为添加按钮添加点击事件:

//添加按钮点击事件 
private void button1_Click(object sender, EventArgs e)
        {
            //实例化EmployeeInfo类
            EmployeeInfo info = new EmployeeInfo();
            info.EmployeeName=textBox1.Text;
            info.EmployeeNo=textBox2.Text;
            info.EmployeeAge=int.Parse(textBox3.Text);
            info.EmployeeSalary=int.Parse(textBox4.Text);
            info.EmployeeJob=textBox5.Text;
            //给类中的每个字段赋值
            EmpBLL empBLL = new EmpBLL();
            //实例化BLL业务逻辑层的EmpBLL方法类,调用其中的添加方法把info作为参数传入
            int r= empBLL.EmpInsert(info);
            //用变量接收返回值来判断受影响行数
            if (r > 0) { 
                MessageBox.Show("添加成功"); 
                Form2 form=this.Owner as Form2;
                form.select();
                this.Close();
                
            
            } else { MessageBox.Show("添加失败"); }
        }

Form2 form=this.Owner as Form2:

这段的理解是:将当前窗体的拥有者窗体转换为 Form2 类型,并将转换后的实例赋值给了 form 变量。这样,我们就可以直接访问该窗体对象的属性和方法,例如 form 属性或者 Show 方法等。需要注意的是,如果 this.Owner 不是 Form2 类型的对象,则 form 变量将为 null。

之后再调用form2窗体的select()方法实现添加成功之后刷新form2的datagridview的集合

剩余的几个按钮就不一一进行讲解了,原理同上,有时间可以自己多练习熟能生巧,该文章只供参考用于理解软件开发模式的三层架构,相信认真看了的小伙伴能够看出对于传统的一个控件一个控件来完善的方式节省的代码量是非常可观的。

最后文章到这里就结束了,如果感觉对你有帮助的话可以点赞或收藏来加深理解。

再感谢认真看完文章的人对我的认可,本人会持续更新更多对粉丝有用的文章喜欢的可以关注支持哦文章来源地址https://www.toymoban.com/news/detail-494790.html

到了这里,关于.net 软件开发模式——三层架构的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据驱动开发模式将软件开发过程改造成一个公式化的迭代模式,可以提升软件开发效率,缩短开发周期,降低开发成本。

    作者:禅与计算机程序设计艺术 随着云计算、大数据等新兴技术的应用,软件开发领域迎来了蓬勃发展的时期。各种编程语言、框架、工具不断涌现,协同工作的强烈需求已经成为当今社会的一个主要挑战。这就需要一种新的开发方式来适应这种复杂多变的环境。传统的瀑布

    2024年02月06日
    浏览(42)
  • 一个.Net Core开发的开源动态壁纸软件

    推荐一个Github上Start超过10.8K的超火、好用、强大的、内置很多优美的动态壁纸软件。 这是基于.Net Core+WPF开发的、开源的动态壁纸软件,壁纸设置支持任何文件形式,包括:本地视频、网络视频、图片、Html、网络、Unity单机游戏、Godot游戏等;还支持自定义壁纸的属性,自定

    2024年02月05日
    浏览(34)
  • 🔥🔥微服务架构:软件开发的革命还是短暂潮流?

    从今天开始,我们将深入探讨服务网格(Service Mesh)这个领域的知识。尽管在我们的工作中可能还没有广泛应用,但服务网格确实是一种趋势。如果你还没有听说过这个概念,我希望你能够跟随我的步伐,一起了解这个特殊而重要的技术。首先,我将为大家介绍微服务的发展

    2024年02月05日
    浏览(46)
  • .NET6: 开发基于WPF的摩登三维工业软件 (7)

    Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 做为一个摩登的工业软件,提供可编程的脚本能力是必不可少的能力。脚本既可以方便用户进行二次开发,也对方便对程序进行自动化测试。本文将结合

    2024年02月05日
    浏览(31)
  • 设计模式: 软件设计的分层与软件开发注意事项

    软件设计的分层 系统级设计架构 应用级架构 模块级架构 代码级架构 1) 系统级设计架构 应用在整个系统内,如与后台服务如何通信,与第三方系统如何集成 包括业务的关系和协作的机制 设计后端:与后台数据传递的机制 包括:api设计规则,访问授权的一个开放标准(OAuth

    2024年02月07日
    浏览(35)
  • 汽车软件开发模式的5个特点

    汽车软件开发属于较为复杂的系统工程,经常让来自不同知识背景的工程师在观点交锋时出现分歧。在解决复杂性和对齐讨论基准时,可以通过勾勒出讨论对象最关键的几个特征来树立典型概念。本文旨在通过5个典型特点的抽取,来勾勒出汽车软件开发模式的特殊性和变迁性

    2024年02月19日
    浏览(22)
  • 【软件开发】大规模分布式系统的容错架构设计

    假设有一个数据库,数据库里有一张特别大的表,里面有几十亿,甚至上百亿的数据。更进一步说,假设这一张表的数据量多达几十个 TB,甚至上百个 TB,那么如果用 MySQL 之类的数据库,单台数据库服务器上的磁盘可能都不够放这一张表的数据! 假如你手头有一个超大的数

    2024年02月04日
    浏览(32)
  • 进出口跨境电商软件平台系统开发,源码技术架构

    一、进出口跨境电商软件平台系统开发需做好相应的前期准备,如确定市场、了解政策、推广宣传等。 欢迎名片沟通探讨 确定目标市场:选择合适的目标市场。需要了解目标市场的消费习惯、政策法规以及竞争情况。 了解海关相关政策:针对不同国家或地区的海关政策可能

    2024年02月08日
    浏览(40)
  • 软考 系统架构设计师系列知识点之基于架构的软件开发方法ABSD(6)

    接前一篇文章:软考 系统架构设计师系列知识点之基于架构的软件开发方法ABSD(5) 所属章节: 第7章. 系统架构设计基础知识         第5节. 特定领域软件体系结构 相关试题 1. 基于架构的软件设计(ABSD)强调由商业、质量和功能需求的组合驱动软件架构设计。ABSD方法

    2024年02月07日
    浏览(40)
  • 敏捷:应对软件定义汽车时代的开发模式变革

    ​ 随着软件定义汽车典型应用场景的落地,汽车从交通工具转向智能移动终端的趋势愈发明显。几十年前,一台好车的定义主要取决于高性能的底盘操稳与动力系统;几年前,一台好车的定义主要取决于智能化系统与智能交互能否满足终端用户的用车体验;相信不久后的将来

    2024年02月05日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包