文章目录
- 系列文章
- 1.项目搭建
- 1.1 新建Asp.net core MVC项目
- 1.2 ASP.NET Core MVC目录结构
- 1.3 创建一个控制器,与页面数据交互
- 1.4 实现一个登录页面
- 1.5 实现后台管理主界面
- 2.过程中知识点和涉及到的问题
- 2.1 session的使用
- 2.2 EF Core连接mysql
- 源码下载
作者:xcLeigh
文章地址:https://blog.csdn.net/weixin_43151418/article/details/131458964
asp.net core 框架搭建2-搭建MVC后台管理系统
,本文章介绍asp.net core框架搭建,然后开发一个后台管理系统,将一步步带着大家,实现目标。所有操作过程将展现在本篇文章,下面咋们一起来实现它吧。
使用:VS2022+EF core+Asp.net core 6+mysql
系列文章
- asp.net core 框架搭建1-搭建webapi
- asp.net core 框架搭建2-搭建MVC后台管理系统
- asp.net core 框架搭建3-搭建个人博客,公司官网
- asp.net core 框架搭建4-部署IIS/nguix
1.项目搭建
MVC 是软件工程的架构方式
- 模型(Model)、视图(View)、控制器(Controller)
可以轻松分离业务、数据显示、逻辑控制 - 视图(View)是用户交互界面,仅展示数据,不处理数据,接收用户输入
- 模型(Model)是 MVC 架构核心,表示业务模型或者数据模型。包含了业务逻辑,如算法实现、数据的管理、输出对象的封装等等
- 控制器(Controller)是接收用户的输入,调用模型和视图完成用户的请求。不处理数据,仅接收用户请求,决定调用哪个模型处理,根据模型的返回,觉得使用哪个视图来显示数据
1.1 新建Asp.net core MVC项目
第一步 打开VS2022
第二步 创建新项目
第三步 创建Asp.net core MVC
第四步 配置项目
第五步 配置相关信息,然后创建
第六步 运行项目(方式1:直接按F5;方式2:右键Views>Home>Index.cshtml,在浏览器中查看),查看效果
到此项目的架子就搭起来了。
1.2 ASP.NET Core MVC目录结构
Connected Services:用于添加服务依赖项。
Prorerties:用于配置项目文件
launchSettings.json:用于配置项目启动协议,默认含有Command命令启动协议和IIS启动协议,可更改相关启动协议端口号等。
wwwroot:用于存放css、js、favicon.ico图标等静态文件。
依赖项:用于管理NuGet程序包等。
Controllers:控制器。MVC中的C。用于业务逻辑计算或调用其他服务。内含相关的控制器,默认含有HomeController控制器。
Models:模型。MVC中的M。用于实体对象,保存数据,传输数据。内含相关的模型,默认含有ErrorViewModel模型。
Views:视图。MVC中的V。用于展示数据、,表现数据。
Home文件夹:内含Home控制器需要的视图文件,文件名称与Home控制器中的方法相对应。
Shared文件夹:内含_Layout.cshtml母版视图、_ValidationScriptsPartial.cshtml脚本验证视图、Error.cshtml错误视图。
_ViewImports.cshtml:用于声明项目命名空间视图。
_ViewStart.cshtml:用于启动母版视图配置。
appsettings.json:配置文件。
Program.cs:程序入口,启动入口。
Program.cs中间件说明
//将Http请求重定向到Https
app.UseHttpsRedirection();
//使能够提供HTML、CSS、映像和Js等静态文件。
app.UseStaticFiles();
//向中间件管道添加路由配置。
app.UseRouting();
//授权用户访问安全资源
app.UseAuthorization();
//为Razor Pages 配置终结点路由
app.MapRazorPages();
//运行应用
app.Run();
1.3 创建一个控制器,与页面数据交互
第一步 打开系统默认创建的HomeController,创建一个xcLeigh控制器,并添加视图
public ActionResult xcLeigh() {
MyInfoModel myInfoModel = new MyInfoModel();
myInfoModel.Name = "xcLeigh";
myInfoModel.Token = "DDMY00000001";
myInfoModel.Time = "20230704134040";
myInfoModel.Description = "参数记录";
return View(myInfoModel);
}
第二步 创建一个模型MyInfoModel
public class MyInfoModel
{
public string Name { get; set; }
public string Token { get; set; }
public string Time { get; set; }
public string Description { get; set; }
}
第三步 视图接收返回值
<div>
姓名:@Model.Name
</div>
<div>
密钥:@Model.Token
</div>
<div>
时间:@Model.Time
</div>
<div>
描述:@Model.Description
</div>
运行效果如下:
1.4 实现一个登录页面
- 第一步 新建登录界面的控制器
数据连接配置见文章: 2.2 EF Core连接mysql
AlanDao类代码
public class AlanDao : IAlanDao
{
public AlanContext Context;
public AlanDao(AlanContext context)
{
Context = context;
}
public bool CreateUser(UserModel user)
{
Context.User.Add(user);
return Context.SaveChanges() > 0;
}
public bool DeleteUserByID(int id)
{
var User = Context.User.SingleOrDefault(s => s.Id == id);
Context.User.Remove(User);
return Context.SaveChanges() > 0;
}
public UserModel GetUserByID(int id)
{
return Context.User.SingleOrDefault(s => s.Id == id);
}
public UserModel GetUserByLogin(string username,string password)
{
return Context.User.SingleOrDefault(s => s.UserName == username && s.PassWord==password);
}
public IEnumerable<UserModel> GetUsers()
{
return Context.User.ToList();
}
public bool UpdateNameByID(int id, string name)
{
var state = false;
var User = Context.User.SingleOrDefault(s => s.Id == id);
if (User != null)
{
User.UserName = name;
state = Context.SaveChanges() > 0;
}
return state;
}
public bool UpdateUser(UserModel User)
{
Context.User.Update(User);
return Context.SaveChanges() > 0;
}
}
HomeController里面Login代码
public ActionResult Login(string username, string password)
{
if (username == null || password == null)
{
return View();
}
if (username.Equals("") || username.Length < 4 || password.Equals("") || password.Length < 4)
{
ViewBag.Mess = "<span style='color:red;'>请输入正确的用户名密码</span>";
}
else
{
UserModel user = AlanDao.GetUserByLogin(username,password);
if (user == null)
{
ViewBag.Mess = "<span style='color:red;'>请输入正确的用户名密码</span>";
}
else
{
HttpContext.Session.SetString("username", username);
//ViewBag.Mess = HttpContext.Session.GetString("username");
return RedirectToAction("Index", "Home");//控制跳控制
}
}
return View();
}
- 第二步 添加登录视图
Login.cshtml界面代码
@{
ViewData["Title"] = "登录 - 后台管理系统";
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>@ViewData["Title"]</title>
<link href="~/favicon.ico" rel="icon">
<link rel="stylesheet" href="/login/css/bootstrap.css">
<link href="/login/iconfont/style.css" type="text/css" rel="stylesheet">
<link href="/login/css/login.css" type="text/css" rel="stylesheet" />
</head>
<body style="background:url('/login/images/bg.jpg') no-repeat; background-size: cover;">
<div class="container wrap1" style="height:450px;">
<h2 class="mg-b20 text-center"> </h2>
<div class="col-sm-8 col-md-5 center-auto pd-sm-50 pd-xs-20 main_content">
@*<p class="text-center font16">后台管理系统</p>*@
<h2 class="mg-b20 text-center" style="margin-top:0px;">后台管理系统 </h2>
<form action="/Home/Login" method="post">
<div class="form-group mg-t20">
<i class="icon-user icon_font"></i>
<input type="text" class="login_input" id="username" name="username" placeholder="请输入用户名" />
</div>
<div class="form-group mg-t20">
<i class="icon-lock icon_font"></i>
<input type="password" class="login_input" id="password" name="password" placeholder="请输入密码" />
</div>
<div class="checkbox mg-b25">
<label>
@*<input type="checkbox" />记住密码*@
<span>@Html.Raw(ViewBag.Mess)</span>
</label>
</div>
<button class="login_btn">登 录</button>
</form>
</div><!--row end-->
</div><!--container end-->
</body>
</html>
- 运行效果
1.5 实现后台管理主界面
- 第一步 新建Index主界面控制器
public ActionResult Index()
{
string str=HttpContext.Session.GetString("username");
if (str == null || str == "")
{
//ViewBag.Mess = HttpContext.Session.GetString("username");
return RedirectToAction("Login", "Home");//控制跳控制
}
else
{
ViewBag.UserName = HttpContext.Session.GetString("username");
ViewBag.UserInfo = AlanDao.GetUsers();
return View();
}
}
- 第二步 新建主界面视图
@{
ViewData["Title"] = "Index";
Layout = null;
}
<!DOCTYPE html>
<html lang="zh" class="no-js">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>后台管理系统</title>
<link href="~/favicon.ico" rel="icon">
<link rel="stylesheet" type="text/css" href="~/layui/css/layui.css" />
<link rel="stylesheet" href="~/main/css/reset.css"> <!-- CSS reset -->
<link rel="stylesheet" type="text/css" href="~/main/css/default.css">
<link rel="stylesheet" href="~/main/css/style.css"> <!-- Resource style -->
<script src="~/main/js/modernizr.js"></script> <!-- Modernizr -->
<script type="text/javascript" src="~/layui/layui.js"></script>
<style type="text/css">
table td{
border:1px solid #3E454C;
padding:8px 10px;
}
</style>
</head>
<body>
<header class="cd-main-header">
<a href="#0" class="cd-logo" style="font-size: 22px;letter-spacing: 4px; color:white; font-family: 华文中宋;">
后台管理系统
</a>
<!-- <div style="color: white;">
后台管理系统
</div> -->
<!-- <div class="cd-search is-hidden">
<form action="#0">
<input type="search" placeholder="Search...">
</form>
</div> -->
<a href="#0" class="cd-nav-trigger">菜单<span></span></a>
<nav class="cd-nav">
<ul class="cd-top-nav">
<li><a href="https://blog.csdn.net/weixin_43151418/article/details/130884793" target="_blank">大屏模板</a></li>
<li><a href="https://blog.csdn.net/weixin_43151418/article/details/131206421" target="_blank">登录模板</a></li>
<li><a href="https://blog.csdn.net/weixin_43151418/article/details/128256066" target="_blank">后台模板</a></li>
<li><a href="https://blog.csdn.net/weixin_43151418/article/details/128315955" target="_blank">表白模板</a></li>
<li><a href="https://blog.csdn.net/weixin_43151418/article/details/131273315" target="_blank">简历模板</a></li>
<li class="has-children account">
<a href="#0">
<img src="/main/img/cd-avatar.png" alt="avatar">
@ViewBag.UserName
</a>
<ul>
<li><a href="#0">个人信息</a></li>
<li><a href="#0">修改个人信息</a></li>
<li><a href="#0">注销</a></li>
</ul>
</li>
</ul>
</nav>
</header> <!-- .cd-main-header -->
<main class="cd-main-content">
<nav class="cd-side-nav">
<ul>
<li class="cd-label">主界面</li>
<li class="has-children overview">
<a href="#0">基础数据</a>
<ul>
<li><a href="#0">运维数据</a></li>
<li><a href="#0">系统数据</a></li>
<li><a href="#0">平台数据</a></li>
</ul>
</li>
<li class="has-children notifications active">
<a href="#0">系统消息<span class="count">13</span></a>
<ul>
<li><a href="#0">所有消息</a></li>
<li><a href="#0">运维消息</a></li>
<li><a href="#0">平台消息</a></li>
</ul>
</li>
<li class="has-children comments">
<a href="#0">系统文档</a>
<ul>
<li><a href="#0">运维文档</a></li>
<li><a href="#0">平台文档</a></li>
<li><a href="#0">扩展文档</a></li>
</ul>
</li>
</ul>
<ul>
<li class="cd-label">常用工具</li>
<li class="has-children bookmarks">
<a href="#0">系统书签</a>
<ul>
<li><a href="#0">全部书签</a></li>
<li><a href="#0">运维书签</a></li>
<li><a href="#0">平台书签</a></li>
</ul>
</li>
<li class="has-children images">
<a href="#0">应用管理</a>
<ul>
<li><a href="#0">平台应用</a></li>
<li><a href="#0">运维应用</a></li>
</ul>
</li>
<li class="has-children users">
<a href="#0">系统信息</a>
<ul>
<li><a href="#0">用户管理</a></li>
<li><a href="#0">角色管理</a></li>
<li><a href="#0">权限管理</a></li>
</ul>
</li>
</ul>
<ul>
<li class="cd-label">辅助应用</li>
<li class="action-btn"><a target="_blank" href="https://blog.csdn.net/weixin_43151418/article/details/131273315">xcLeigh</a></li>
</ul>
</nav>
<div class="content-wrapper">
<div style="position: absolute; width: calc(100% - 200px); height: calc(100% - 55px); background-color:aliceblue;">
<div style="padding: 40px;">
这里是右侧内容,可以放IFRAME
<div style="padding:20px; border-bottom:1px solid #3E454C;">
用户信息列表
<a href="javascript:void(0);" style="float:right;" onclick="operUser('');">新增</a>
</div>
<div style="width:100%;">
<table style="border:1px solid #3E454C; margin-top:10px; width:100%; text-align:center;">
<tr style="background-color:#E8EAED;">
<td>序号</td>
<td>用户名</td>
<td>密码</td>
<td>电话</td>
<td>邮箱</td>
<td>地址</td>
<td>操作</td>
</tr>
@if (ViewBag.UserInfo == null)
{
<tr>
<td colspan="6"> 暂无数据</td>
</tr>
}
else
{
for (int i = 0; i < ViewBag.UserInfo.Count; i++)
{
<tr>
<td>@(i+1)</td>
<td>@ViewBag.UserInfo[i].UserName</td>
<td>@ViewBag.UserInfo[i].PassWord</td>
<td>@ViewBag.UserInfo[i].Tel</td>
<td>@ViewBag.UserInfo[i].Email</td>
<td>@ViewBag.UserInfo[i].Addr</td>
<td>
<a href="javascript:void(0);" onclick="delUser(@ViewBag.UserInfo[i].Id);">删除</a>
<a href="javascript:void(0);" onclick="operUser('@ViewBag.UserInfo[i].Id');">编辑</a>
</td>
</tr>
}
}
</table>
</div>
</div>
</div>
</div> <!-- .content-wrapper -->
</main> <!-- .cd-main-content -->
<script src="~/main/js/jquery-2.1.1.min.js"></script>
<script src="~/main/js/jquery.menu-aim.js"></script>
<script src="~/main/js/main.js"></script> <!-- Resource jQuery -->
<script type="text/javascript">
layui.use(['element', 'carousel', 'jquery', 'layer'], function() {
var element = layui.element;
var carousel = layui.carousel;
var $ = layui.$;
var layer = layui.layer;
});
function operUser(czlx){
var index = layer.open({
type: 2,
title: "用户信息管理",
maxmin: true,
area: ['450px', '350px'],
content: '/Home/OperUser?czlx='+czlx,
cancel: function (index, layero) {//取消事件
},
end: function () {//无论是确认还是取消,只要层被销毁了,end都会执行,不携带任何参数。layer.open关闭事件
window.location.reload();
//showTable();//location.reload(); //layer.open关闭刷新
}
});
}
function delUser(record){
console.log(record);
$.ajax({
type: 'POST',
contentType: "application/json",
url: "/Home/DelUser?id="+record,
//data: "{id:" + record + "}",
dataType: "json",
success: function (resultData) {
console.log(resultData);
if(resultData){
layer.msg("删除成功");
setTimeout(function(){
window.location.reload();
},1000);
}else{
layer.msg("删除失败");
setTimeout(function(){
window.location.reload();
},1000);
}
}
});
}
</script>
</body>
</html>
效果图
- 第三步 新建数据操作控制器
public ActionResult OperUser(string czlx, string username,string password,string tel,string addr,string email) {
ViewBag.czlx = czlx;
ViewBag.UserInfo = new UserModel() {
};
if (czlx == "" || czlx == null)
{
if (username == "" || username == null)
{
//不做任何操作
}
else {
var user = new UserModel()
{
UserName = username,
PassWord = password,
Tel = tel,
Email = email,
Addr = addr
};
var result = AlanDao.CreateUser(user);
if (result)
{
ViewBag.Message = "操作成功";
}
else
{
ViewBag.Message = "操作失败";
}
}
}
else {
if (username == "" || username == null)
{
ViewBag.UserInfo = AlanDao.GetUserByID(int.Parse(czlx));
}
else
{
var user = new UserModel()
{
Id=int.Parse(czlx),
UserName = username,
PassWord = password,
Tel = tel,
Email = email,
Addr = addr
};
var result = AlanDao.UpdateUser(user);
if (result)
{
ViewBag.Message = "操作成功";
}
else
{
ViewBag.Message = "操作失败";
}
}
}
return View();
}
- 第四步 新建数据操作视图
@{
ViewData["Title"] = "用户管理";
Layout = "~/Views/Shared/_xcLeighLayout.cshtml";
}
<form id="form1" class="layui-form" action="/Home/OperUser">
<input type="text" value="@ViewBag.czlx" id="czlx" name="czlx" hidden="hidden" />
<div class="layui-form-item">
<table class="tableContent">
<tr>
<th><nobr>用户名称:</nobr></th>
<td>
<input name="username" value="@ViewBag.UserInfo.UserName" class="layui-input" type="text" placeholder="请输入用户名称" autocomplete="off" lay-verify="required" lay-reqtext="用户名称是必填项,岂能为空?">
</td>
</tr>
<tr>
<th>用户密码:</th>
<td>
<input name="password" value="@ViewBag.UserInfo.PassWord" class="layui-input" type="text" placeholder="请输入用户密码" autocomplete="off" lay-verify="required" lay-reqtext="用户密码是必填项,岂能为空?">
</td>
</tr>
<tr>
<th>邮箱:</th>
<td><input name="email" value="@ViewBag.UserInfo.Email" class="layui-input" type="text" placeholder="请输入邮箱" autocomplete="off" lay-verify="required|email" lay-reqtext="邮箱是必填项,岂能为空?"></td>
</tr>
<tr>
<th>电话:</th>
<td><input name="tel" value="@ViewBag.UserInfo.Tel" class="layui-input" type="text" placeholder="请输入电话" autocomplete="off" lay-verify="required|phone" lay-reqtext="电话是必填项,岂能为空?"></td>
</tr>
<tr>
<th>地址:</th>
<td><input name="addr" value="@ViewBag.UserInfo.Addr" class="layui-input" type="text" placeholder="请输入部门编号" autocomplete="off" lay-verify="required" lay-reqtext="地址是必填项,岂能为空?"></td>
</tr>
<tr>
<th></th>
<td>
<nobr>
<button class="layui-btn" type="submit" lay-filter="demo1" lay-submit="">立即提交</button>
<button class="layui-btn layui-btn-primary" type="reset">重置</button>
</nobr>
</td>
</tr>
</table>
</div>
</form>
<script type="text/javascript">
layui.use(['form', 'layedit', 'laydate'], function(){
var form = layui.form
,layer = layui.layer
//自定义验证规则
form.verify({
phone: [/^1\d{10}$/, "请输入正确的手机号"],
email: [/^([a-zA-Z0-9_\.\-])+\@@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/, "邮箱格式不正确"],
url: [/(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/, "链接格式不正确"],
number: function(e) {if (!e || isNaN(e)) return "只能填写数字"},
identity: [/(^\d{15}$)|(^\d{17}(x|X|\d)$)/, "请输入正确的身份证号"],
chinese: [/^[\u4e00-\u9fa5]+$/, "只能为中文"],
english: [/^[a-zA-Z]+$/,"只能为英文"]
});
//监听提交
form.on('submit(demo1)', function (data) {
});
});
$(function () {
var mes = "@Html.Raw(ViewBag.Message)";
var czlx = "@ViewBag.czlx";
if (czlx == "add" || czlx == "") {
} else {
}
if (mes == "") {
} else {
//layer.msg(mes, {icon: 6});
//配置一个透明的询问框
setTimeout(function(){
layer.msg("操作成功,2秒后关闭!", {
icon: 1,
time: 2000,
shade: 0.3,
end: function () {
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
}
});
},200);
}
});
</script>
效果图
至此框架系统基本操作就完成了,实现了与数据库交互操作;实现了界面与后台连接;实现了界面框架设计;实现了登录界面;实现了管理系统界面,首界面的增删改查。其他操作在基本框架上借用就行了。下面大家自己探索更多的功能吧。
2.过程中知识点和涉及到的问题
2.1 session的使用
在ASP.NET MVC中,session可以直接使用,但是在ASP.NET core MVC中,需要配置引用,然后才能使用。
第一步 安装包引用
从nuget安装
1 Microsoft.AspNetCore.Mvc
2 Microsoft.AspNetCore.Http
第二步 配置
在Program.cs文件中的builder里配置以下两项(两个builder),里面的第一个builder就是注册Session服务,第二builder是打开启用上下文(这是最关键的,也是为什么其它教程的Session不能跨Controller访问的原因)
//使ASP.NET CORE能使用SESSION 1
builder.Services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(30);
options.Cookie.HttpOnly = false;
});
//使ASP.NET CORE能使用SESSION 2 放到app.UseRouting();后面
app.UseSession();
第三步 控制器里面读取
HttpContext.Session.SetString("username","1234");
ViewBag.Mess = HttpContext.Session.GetString("username");
2.2 EF Core连接mysql
第一步 NUGET添加引用
Pomelo.EntityFrameworkCore.MySql
第二步 配置appsetting.json和program.cs
appsetting.json
"ConnectionStrings": {
"MysqlConnection": "Data Source=IP地址;Database=数据库;User ID=用户名;Password=密码;port=端口号;sslmode=none;CharSet=utf8;pooling=true;"
},
programs.cs
builder.Services.AddDbContext<AlanContext>(option => {
string connStr = builder.Configuration.GetConnectionString("MysqlConnection");
option.UseMySql(connStr, ServerVersion.AutoDetect(connStr), null);
});
第三步 新建实体类 UserModel
[Table("sys_user")]
public class UserModel
{
[Key] //主键
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] //设置自增
[Column("id")]
public int Id { get; set; }
[Column("username")]
public string UserName { get; set; }
[Column("password")]
public string PassWord { get; set; }
[Column("tel")]
public string Tel { get; set; }
[Column("email")]
public string Email { get; set; }
[Column("addr")]
public string Addr { get; set; }
}
第四步 新建DbContext
*注意:DbSet里的属性名和表名保持一致。
public class AlanContext : DbContext
{
public AlanContext(DbContextOptions<AlanContext> options)
: base(options)
{ }
public DbSet<UserModel> User { get; set; }
}
第五步 控制器调用
private readonly AlanContext _Context;
public SystemController(AlanContext _context)
{
_Context=_context;
}
// GET: api/<vvController>
[HttpGet]
public IEnumerable<string> Get()
{
try
{
var model= _Context.User.FirstOrDefault();
if (model != null)
{
return new string[] { "value1", model.username};
}
}
catch (Exception ex)
{
return new string[] { "value1", ex.Message };
}
return new string[] { "value1", "value2" };
}
源码下载
asp.net core 框架搭建2-搭建MVC后台管理系统(源码) 点击下载
💢 关注博主 带你实现畅游前后端
🏰 加入社区 带你体验马航不孤单
💯 神秘个人简介 带你体验不一样得介绍
💘 为爱表白 为你那个TA,体验别致的浪漫惊喜
🎀 酷炫邀请函 带你体验高大上得邀请
① 🉑提供云服务部署(有自己的阿里云);
② 🉑提供前端、后端、应用程序、H5、小程序、公众号等相关业务;
如🈶合作请联系我,期待您的联系。
注:本文撰写于CSDN平台,作者:xcLeigh(所有权归作者所有),https://blog.csdn.net/weixin_43151418,如果相关下载没有跳转,请查看这个地址,相关链接没有跳转,皆是抄袭本文,转载请备注本文原地址。
亲,码字不易,动动小手,欢迎 点赞 ➕ 收藏,如 🈶 问题请留言(评论),博主看见后一定及时给您答复,💌💌💌
原文地址:https://blog.csdn.net/weixin_43151418/article/details/131458964(防止抄袭,原文地址不可删除)