工具
visual studio 2022
以下简称vs Visual Studio安装指南_visual studio安装教程_技术人小柒的博客-CSDN博客
sqlserver 2022
以下简称mssql SQL Server2022 Express和SSMS下载安装教程(超详细) (baidu.com)
redis
Redis下载安装图文教程(Windows版_超详细)_windows redis下载_Leeway啊樺的博客-CSDN博客
PDman
数据库设计,可以直接生成脚本,power design平替 PDMan数据库建模
创建项目
创建解决方案
打开vs点创建新项目,没什么可说的,有手就行,下一步
搜索框输入core,因为.net和.net core又在一起了。选择asp.net core web api,下一步
项目名称最好写的规范一点,一般都是 [公司名称.项目名称.net项目名称]和一些天花乱坠的标识,解决方案和项目放在同一目录去掉,很烦,项目总体的目录架构不太好看,然后把解决方案名称最好改掉,因为解决方案是项目的父级,最好是比项目名少一个节点。下一步
最后一步选择.net7版本,https勾掉,安全机制影响开发效率,有需要的加上就好。点创建。
完成,成功创建一个api项目,直接可以跑,把docker换成iis express
跑起来看到是core内置的swagger,方便测试服务接口用 try it out ==> Excute ,200就是成功。
项目层级
根据项目实际需要分层,我这里分了七层:(懒得重新创建了,直接用之前的项目截图)
从上到下分别是:
1.API接口层, 用于实现各种场景需求的服务,主要存放controller控制器
2.Cache缓存
3.Common辅助类层,存放一些公用的方法和帮助类
4.DAL数据访问层,主要一些基本的操作数据库的方法。
5.IRepository抽象层,因为我们用到了ioc容器,所以保存了一些控制反转的机制,用于存放抽象类
6.Model实体层,存放数据实体类,包括实现特定需求业务的实体,枚举,接口响应数据类型,调用其他接口的请求类型
7.Repository 业务逻辑层,需要实现上面的抽象层,达到控制反转的效果,同时相当于三层架构中的BLL层级,实现各项实际业务。
根据实际场景缩减或扩展。
实现登录功能
数据结构
表结构仅供参考,根据实际需求创建 。
这里创建好了直接生成脚本去执行
实体创建
然后在项目的model层创建一个entity文件夹,创建一个用户的实体类
内容:
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Project.TM.WCore.Model.Entity.JCXX
{
/// <summary>
/// 用户信息表
/// </summary>
[Table("T_JCXX_User")]
public class UserEntity
{
/// <summary>
/// ID
/// </summary>
[Dapper.Key]
public int Id { get; set; }
/// <summary>
/// 名称
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 用户名
/// </summary>
public string? UserName { get; set; }
/// <summary>
/// 密码
/// </summary>
public string? Password { get; set; }
/// <summary>
/// 性别
/// </summary>
public bool Sex { get; set; }
/// <summary>
/// 年龄
/// </summary>
public int Age { get; set; }
/// <summary>
/// 生日
/// </summary>
public string? Birthday { get; set; }
/// <summary>
/// 联系方式
/// </summary>
public string? Contact { get; set; }
/// <summary>
/// 职称
/// </summary>
public string? Occupation { get; set; }
/// <summary>
/// 部门
/// </summary>
public string? Department { get; set; }
/// <summary>
/// 邮箱
/// </summary>
public string? Email { get; set; }
/// <summary>
/// 居住地
/// </summary>
public string? City { get; set; }
/// <summary>
/// 公司
/// </summary>
public string? Company { get; set; }
/// <summary>
/// 头像
/// </summary>
public string? Avatar { get; set; }
/// <summary>
/// 标签
/// </summary>
public int Tag { get; set; }
/// <summary>
/// 个人介绍
/// </summary>
public string? Introductory { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool Isuse { get; set; }
/// <summary>
/// 部门/组织/角色
/// </summary>
public string[]? role { get; set; }
/// <summary>
/// 创建人
/// </summary>
public string? Creator { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime Createtime { get; set; }
/// <summary>
/// 更新人
/// </summary>
public string? Updator { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime Updatetime { get; set; }
}
}
创建dapperhelper帮助类
这里我们用到dapperDapper简介 - 非法关键字 - 博客园 (cnblogs.com)
ORM框架简介_orm框架是什么意思_Courage-Hu的博客-CSDN博客
实现对数据库的连接和增删改查基本方法
在common层创建一个DapperHelper类
nuget:
直接把数据库连接串替换成你自己的,内容:
using System.Data.SqlClient;
using System.Data;
using Dapper;
namespace Project.TM.WCore.Common
{
/// <summary>
/// Dapper帮助类
/// </summary>
public class DapperHelper
{
#region 连接
/// <summary>
/// 数据库连接串
/// </summary>
public static string ConnectionString = string.Empty;
/// <summary>
/// 注册一个连接事件
/// </summary>
public static IDbConnection? connection = null;
/// <summary>
/// 构造函数
/// </summary>
public DapperHelper()
{
//ConnectionString = ConnectionProvider.GetConnection();
ConnectionString = SettingProvider.GetConnection();
//ConnectionString = ConfigurationManager.ConnectionStrings["SqlConnetion"].ConnectionString;
}
/// <summary>
/// 获得conn对象
/// </summary>
/// <returns></returns>
public IDbConnection GetConn()
{
return new SqlConnection(ConnectionString);
}
/// <summary>
/// 打开conn
/// </summary>
/// <param name="conn"></param>
public void OpenConn(IDbConnection conn)
{
conn.Open();
}
/// <summary>
/// 销毁conn
/// </summary>
/// <param name="conn"></param>
public void DisposeConn(IDbConnection conn)
{
conn.Dispose();
conn.Close();
}
#endregion
#region 业务类
/// <summary>
/// 查询.
/// </summary>
/// <typeparam name="T">实体类型.</typeparam>
/// <param name="sql">sql执行语句.</param>
/// <returns>泛型类.</returns>
public static List<T> Query<T>(string sql)
where T : class
{
using (connection = new SqlConnection(ConnectionString))
{
return connection.Query<T>(sql).ToList();
}
}
/// <summary>
/// 查询单条记录
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="obj">new{...}</param>
/// <returns></returns>
public T ExecuteQuery<T>(string sql, object? obj = null)
{
T result;
using (connection = new SqlConnection(ConnectionString))
{
result = connection.QueryFirstOrDefault<T>(sql, obj);
}
return result;
}
/// <summary>
/// 查询多条记录
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="obj">new{...}</param>
/// <returns></returns>
public IEnumerable<T> ExecuteQuerys<T>(string sql, object? obj = null)
{
IEnumerable<T> result;
using (connection = new SqlConnection(ConnectionString))
{
result = connection.Query<T>(sql, obj);
}
return result;
}
/// <summary>
/// 查询指定数据.
/// </summary>
/// <typeparam name="T">实体类型.</typeparam>
/// <param name="sql">sql执行语句.</param>
/// <param name="t">泛型类.</param>
/// <returns>类.</returns>
public static T? Query<T>(string sql, T t)
where T : class
{
using (connection = new SqlConnection(ConnectionString))
{
return connection.Query<T>(sql, t).SingleOrDefault();
}
}
/// <summary>
/// 查询的in操作.
/// </summary>
/// <typeparam name="T">实体类型.</typeparam>
/// <param name="sql">sql执行语句.</param>
/// <returns>泛型类.</returns>
public static List<T> Query<T>(string sql, int[] ids)
where T : class
{
using (connection = new SqlConnection(ConnectionString))
{
return connection.Query<T>(sql, new { ids }).ToList();
}
}
/// <summary>
/// 增删改
/// </summary>
/// <typeparam name="T">
/// <param name="sql"></param>
/// <param name="obj"></param>
/// <returns></returns>
public int ExecuteNonQuery<T>(string sql, T obj)
{
int result = 0;
try
{
using (connection = new SqlConnection(ConnectionString))
{
result = connection.Execute(sql, obj);
}
}
catch { }
return result;
}
/// <summary>
/// 添加
/// </summary>
/// <typeparam name="T">实体类型.</typeparam>
/// <param name="sql">sql执行语句.</param>
/// <param name="t">传入实体类型.</param>
/// <returns>int.</returns>
public static int Add<T>(string sql)
where T : class
{
using (connection = new SqlConnection(ConnectionString))
{
return connection.Execute(sql);
}
}
/// <summary>
/// 新增后获取id
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public static int AddResultId<T>(string sql)
{
int Id = 0;
using (connection = new SqlConnection(ConnectionString))
{
using (SqlCommand command = new SqlCommand(sql, connection as SqlConnection))
{
connection.Open();
Id = Convert.ToInt32(command.ExecuteScalar());
connection.Close();
}
}
return Id;
}
/// <summary>
/// 多语句操作.
/// </summary>
/// <typeparam name="T">实体类型.</typeparam>
/// <param name="sql">sql执行语句.</param>
public static void QueryMultiple(string sql)
{
using (connection = new SqlConnection(ConnectionString))
{
var multiReader = connection.QueryMultiple(sql);
multiReader.Dispose();
}
}
#endregion
#region 事务
/// <summary>
/// 开启事务
/// </summary>
/// <param name="conn"></param>
/// <returns></returns>
public IDbTransaction BeginTransaction(IDbConnection conn)
{
return conn.BeginTransaction();
}
/// <summary>
/// 提交事务
/// </summary>
/// <param name="tran"></param>
/// <param name="conn"></param>
public void Commit(IDbTransaction tran)
{
tran.Commit();
}
/// <summary>
/// 回滚事务
/// </summary>
/// <param name="tran"></param>
/// <param name="conn"></param>
public void Rollback(IDbTransaction tran)
{
tran.Rollback();//回滚
}
/// <summary>
/// 销毁事务
/// </summary>
/// <param name="tran"></param>
/// <param name="conn"></param>
public void Dispose(IDbTransaction tran)
{
tran.Dispose();//销毁
}
/// <summary>
/// 查询带事务
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="conn"></param>
/// <param name="tran"></param>
/// <returns></returns>
public List<T>? ExecuteQuerys<T>(string sql, IDbTransaction tran)
{
try
{
return (List<T>)connection.Query(sql, tran);
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// 使用事务查询多条记录
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="obj">new{...}</param>
/// <returns></returns>
public IEnumerable<T> ExecuteQuerys<T>(string sql, IDbTransaction tran, IDbConnection conn, object? obj = null)
{
IEnumerable<T> result;
result = conn.Query<T>(sql, obj, tran);
return result;
}
/// <summary>
/// 使用事务查询单条记录
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="obj">new{...}</param>
/// <returns></returns>
public T ExecuteQuery<T>(string sql, IDbTransaction tran, IDbConnection conn, object? obj = null)
{
T result;
result = conn.QueryFirstOrDefault<T>(sql, obj, tran);
return result;
}
/// <summary>
/// 增删改带事务
/// </summary>
/// <param name="sql"></param>
/// <param name="tran"></param>
/// <returns></returns>
public int ExecuteNonQuery(string sql, IDbConnection conn, IDbTransaction tran)
{
try
{
int count = conn.Execute(sql, transaction: tran);
return count;
}
catch (Exception)
{
return 0;
}
}
/// <summary>
/// 新增后获取id带事务
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="conn"></param>
/// <param name="tran"></param>
/// <returns></returns>
public int AddResultId<T>(string sql,IDbConnection conn,IDbTransaction tran)
{
try
{
return Convert.ToInt32(new SqlCommand(sql, conn as SqlConnection, tran as SqlTransaction).ExecuteScalar());
}
catch (Exception)
{
return 0;
}
}
#endregion
/// <summary>
/// 获取表名称
/// </summary>
/// <param name="tableName"></param>
public static string GetTable<T>()
{
var tableAttribute = (System.ComponentModel.DataAnnotations.Schema.TableAttribute)typeof(T).GetCustomAttributes(typeof(System.ComponentModel.DataAnnotations.Schema.TableAttribute), true)[0];
return tableAttribute.Name.ToString();
}
}
}
创建数据访问层和增删改查方法
这里创建了两个类上面是用户信息数据访问类,下面是数据访问层继承的基类
这里用基类写一些通用方法,
1.方便业务逻辑层(bll)直接调用,减少代码量
using Project.TM.WCore.Common;
using Project.TM.WCore.Model.Entity.SPLC;
using Project.TM.WCore.Model.Struct;
using System.Data;
namespace Project.TM.WCore.DAL
{
/// <summary>
/// dal基类
/// </summary>
public abstract class IBasicsDal<T> where T : class, new()
{
/// <summary>
/// dapper帮助类
/// </summary>
public DapperHelper dapper;
/// <summary>
/// sql拼接结构体
/// </summary>
public SqlStruct<T> sqlStruct;
/// <summary>
/// 构造函数
/// </summary>
protected IBasicsDal()
{
this.dapper = new DapperHelper();
}
#region 基类虚方法,基本操作方法
/// <summary>
/// 分页查询
/// </summary>
/// <param name="index"></param>
/// <param name="size"></param>
/// <returns></returns>
public virtual Tuple<List<T>, int> GetPageList(int index, int size)
{
List<T> list = dapper.ExecuteQuerys<T>(DisposeSql()).ToList();
return new Tuple<List<T>, int>(list.Skip((index - 1) * size).Take(size).ToList(), list.Count);
}
/// <summary>
/// 获取列表
/// </summary>
/// <returns></returns>
public virtual List<T> GetList()
{
return dapper.ExecuteQuerys<T>(DisposeSql()).ToList();
}
/// <summary>
/// 根据id获取列表
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual T GetListById(int id)
{
return dapper.ExecuteQuery<T>(DisposeSql(id));
}
/// <summary>
/// 根据参数获取列表
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public virtual List<T> GetListByParam(Dictionary<bool, Dictionary<string, string>> param)
{
return dapper.ExecuteQuerys<T>(DisposeSql(0, param)).ToList();
}
/// <summary>
/// 新增
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public virtual bool Add(T entity)
{
return Convert.ToBoolean(dapper.ExecuteNonQuery(DisposeSql(0, entity: entity), entity));
}
/// <summary>
/// 新增返回id
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public virtual int AddResultId(T entity)
{
return DapperHelper.AddResultId<T>(DisposeSql(0, entity: entity, type: 1));
}
/// <summary>
/// 删除
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual bool Delete(int id)
{
return dapper.ExecuteNonQuery<T>(DisposeSql(id, type: 2), null) > 0 ? true : false;
}
/// <summary>
/// 批量删除
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
public virtual bool Deletes(dynamic[] ids)
{
return dapper.ExecuteNonQuery<T>(DisposeSql(0, null, null, ids, 2), null) > 0 ? true : false;
}
/// <summary>
/// 编辑
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public virtual bool Edit(T entity)
{
return dapper.ExecuteNonQuery(DisposeSql(0, null, entity, null, 3), entity) > 0 ? true : false;
}
/// <summary>
/// 批量新增
/// </summary>
/// <param name="entityList"></param>
/// <returns></returns>
public virtual bool AddList(List<T> entityList)
{
foreach (var item in entityList)
{
var result = SqlHelper<T>.Add(item);
if (!result) return result;
}
return true;
}
#endregion
#region 事务操作
/// <summary>
/// 新增并返回id带事务
/// </summary>
/// <param name="Sql"></param>
/// <param name="conn"></param>
/// <param name="tran"></param>
/// <returns></returns>
public int TranAddResultId(string Sql, IDbConnection conn, IDbTransaction tran)
{
int id = dapper.AddResultId<ProjectEntity>(Sql, conn, tran);//新增后获取id
return id;
}
/// <summary>
/// 事务操作(未开启)
/// </summary>
/// <param name="tranDic">1:新增,2:删除,3:修改</param>
/// <returns></returns>
public bool TranEntity(Dictionary<int, string> tranDic)
{
DapperHelper dapper = new DapperHelper();
using (IDbConnection conn = dapper.GetConn())
{
IDbTransaction tran = null;
try
{
dapper.OpenConn(conn);//打开连接
tran = dapper.BeginTransaction(conn);//开启事务
//循环处理数据库操作
foreach (var item in tranDic)
{
switch (item.Key)
{
case 1://新增
dapper.ExecuteNonQuery(item.Value, conn, tran);
break;
case 2://删除
dapper.ExecuteNonQuery(item.Value, conn, tran);
break;
case 3://修改
dapper.ExecuteNonQuery(item.Value, conn, tran);
break;
default:
break;
}
}
dapper.Commit(tran);//提交事务
return true;
}
catch (Exception)//异常回滚
{
dapper.Rollback(tran);
return false;
}
finally
{
if (conn != null)
{
dapper.DisposeConn(conn);//销毁连接
}
if (tran != null)
{
dapper.Dispose(tran);//销毁事务
}
}
}
}
/// <summary>
/// 事务操作(已开启)
/// </summary>
/// <param name="tranDic">1:新增,2:删除,3:修改</param>
/// <returns></returns>
public bool TranEntity(Dictionary<int, string> tranDic, IDbConnection conn, IDbTransaction tran)
{
if (conn == null || tran == null) return false;
try
{
//循环处理数据库操作
foreach (var item in tranDic)
{
dapper.ExecuteNonQuery(item.Value, conn, tran);
}
return true;
}
catch (Exception)
{
return false;
}
}
#endregion
/// <summary>
/// 处理sql
/// </summary>
/// <param name="id"></param>
/// <param name="param"></param>
/// <param name="entity"></param>
/// <param name="ids"></param>
/// <param name="type"></param>
/// <returns></returns>
public string DisposeSql(int id = 0, Dictionary<bool, Dictionary<string, string>>? param = null, T? entity = null, dynamic[]? ids = null, int type = 0)
{
sqlStruct = new SqlStruct<T>();
sqlStruct.Id = id;
sqlStruct.Param = param;
sqlStruct.Entity = entity;
sqlStruct.Ids = ids;
switch (type)
{
case 1:
sqlStruct.IsReturnId = true;
break;
case 2:
sqlStruct.IsDelete = true;
break;
case 3:
sqlStruct.IsUpdate = true;
break;
default: break;
}
string sql = SqlHelper<T>.GetSql(sqlStruct);
return sql;
}
}
}
2.最后获取sql是因为dapper直接操作sql语句,每次都写sql我个人感觉比较麻烦,所以根据不同场景区分了一些通用的sql.在common创建SqlHelper:
using Project.TM.WCore.Model.Struct;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
namespace Project.TM.WCore.Common
{
/// <summary>
/// 获取操作数据sql
/// </summary>
/// <typeparam name="T"></typeparam>
public static class SqlHelper<T> where T : class, new()
{
/// <summary>
/// 实体结构对象
/// </summary>
static TableAttribute TableAttribute { get; set; }
/// <summary>
/// sql拼接参数结构体
/// </summary>
/// <param name="sqlStruct"></param>
/// <returns></returns>
public static string GetSql(SqlStruct<T> sqlStruct)
{
TableAttribute = (TableAttribute)typeof(T).GetCustomAttributes(typeof(TableAttribute), true)[0];
string sql = string.Empty;
if (sqlStruct.Id != 0 && !sqlStruct.IsDelete)//根据id查询
{
sql = GetById(sqlStruct);
}
else if (sqlStruct.Entity != null && !sqlStruct.IsReturnId && !sqlStruct.IsUpdate)//新增
{
sql = AddNew(sqlStruct);
}
else if (sqlStruct.Entity != null && sqlStruct.IsReturnId)//新增并返回id
{
sql = AddNewReturnId(sqlStruct);
}
else if ((sqlStruct.Id != 0 && sqlStruct.IsDelete))//删除
{
sql = Delete(sqlStruct);
}
else if (sqlStruct.Ids != null && sqlStruct.Ids.Count() > 0)//批删
{
sql = DeleteMany(sqlStruct);
}
else if (sqlStruct.Param?.Count > 0 && sqlStruct.IsDelete)//多条件删除
{
sql = DeleteParam(sqlStruct);
}
else if (sqlStruct.Entity != null && sqlStruct.IsUpdate)//更新
{
sql = Update(sqlStruct);
}
else if (sqlStruct.Param?.Count > 0&&!sqlStruct.IsDelete)//多条件查询
{
sql = QueryMany(sqlStruct);
}
else//直接查询
{
sql = $"select * from {TableAttribute.Name}";
}
return sql+";";
}
/// <summary>
/// 多条件查询
/// </summary>
/// <param name="sqlStruct"></param>
/// <returns></returns>
private static string QueryMany(SqlStruct<T> sqlStruct)
{
string sql = $"SELECT * FROM {TableAttribute.Name} WHERE 1=1";
if (sqlStruct.Param?.FirstOrDefault(p => p.Key == true).Value != null)//直接匹配
{
foreach (var item in sqlStruct.Param.FirstOrDefault(p => p.Key == true).Value)
{
sql += $" AND {item.Key} = '{item.Value}'";
}
}
if (sqlStruct.Param?.FirstOrDefault(p => p.Key == false).Value != null)//模糊匹配
{
sql += " AND (";
foreach (var item in sqlStruct.Param?.FirstOrDefault(p => p.Key == false).Value)
{
sql += $" {item.Key} LIKE '%{item.Value}%' OR";
}
sql = sql.Substring(0, sql.Length - 2) + ")";
}
return sql;
}
/// <summary>
/// 更新
/// </summary>
/// <param name="sqlStruct"></param>
/// <returns></returns>
private static string Update(SqlStruct<T> sqlStruct)
{
string sql = $"update {TableAttribute.Name} set";
EntityManage entityManage = GetEntity(sqlStruct.Entity);
foreach (var item in entityManage.keyValuePairs)
{
if (item.Key == "Createtime" || item.Key == "Creator" || item.Key == "Id")
{
continue;
}
if (entityManage.ints.Contains(item.Key))
{
sql += $" {item.Key} = {item.Value} ,";
}
else
{
sql += $" {item.Key} = '{item.Value}' ,";
}
}
sql = sql.Substring(0, sql.Length - 1);
sql += $" where id = {entityManage.keyValuePairs.Where(p => p.Key == "Id").FirstOrDefault().Value}";
return sql;
}
/// <summary>
/// 多条件删除
/// </summary>
/// <param name="sqlStruct"></param>
/// <returns></returns>
private static string DeleteParam(SqlStruct<T> sqlStruct)
{
string sql = $"Delete From {TableAttribute.Name} where 1 = 1";
if (sqlStruct.Param?.FirstOrDefault(p => p.Key == true).Value != null)//直接匹配
{
foreach (var item in sqlStruct.Param.FirstOrDefault(p => p.Key == true).Value)
{
sql += $" AND {item.Key} = '{item.Value}'";
}
}
if (sqlStruct.Param?.FirstOrDefault(p => p.Key == false).Value != null)//模糊匹配
{
sql += " AND (";
foreach (var item in sqlStruct.Param?.FirstOrDefault(p => p.Key == false).Value)
{
sql += $" {item.Key} LIKE '%{item.Value}%' OR";
}
sql = sql.Substring(0, sql.Length - 2) + ")";
}
return sql;
}
/// <summary>
/// 批删
/// </summary>
/// <param name="sqlStruct"></param>
/// <returns></returns>
private static string DeleteMany(SqlStruct<T> sqlStruct)
{
string sql = $"Delete {TableAttribute.Name} where ";
for (int i = 0; i < sqlStruct.Ids.Length; i++)
{
if (i == sqlStruct.Ids.Length - 1)
{
sql += $"id ={sqlStruct.Ids[i]}";
}
else
{
sql += $"id ={sqlStruct.Ids[i]} or ";
}
}
return sql;
}
/// <summary>
/// 删除
/// </summary>
/// <param name="sqlStruct"></param>
/// <returns></returns>
private static string Delete(SqlStruct<T> sqlStruct)
{
if (string.IsNullOrWhiteSpace(sqlStruct.DeleteId)) sqlStruct.DeleteId = "id";
return $"Delete {TableAttribute.Name} where {sqlStruct.DeleteId} = {sqlStruct.Id}";
}
/// <summary>
/// 新增并返回id
/// </summary>
/// <param name="sqlStruct"></param>
/// <returns></returns>
private static string AddNewReturnId(SqlStruct<T> sqlStruct)
{
string values = "";
string names = "";
DisposeSql(sqlStruct.Entity, ref values, ref names);
return $"insert {TableAttribute.Name} ({names.Substring(0, names.Length - 1)}) output inserted.Id values ({values.Substring(0, values.Length - 1)})";
}
/// <summary>
/// 新增
/// </summary>
/// <param name="sqlStruct"></param>
/// <returns></returns>
private static string AddNew(SqlStruct<T> sqlStruct)
{
string values = "";
string names = "";
DisposeSql(sqlStruct.Entity, ref values, ref names);
return $"insert {TableAttribute.Name} ({names.Substring(0, names.Length - 1)}) values ({values.Substring(0, values.Length - 1)})";
}
/// <summary>
/// 根据Id查询
/// </summary>
/// <param name="sqlStruct"></param>
/// <returns></returns>
private static string GetById(SqlStruct<T> sqlStruct)
{
return $"select * from {TableAttribute.Name} where id = {sqlStruct.Id}";
}
/// <summary>
/// 处理sql拼接
/// </summary>
/// <param name="entity"></param>
/// <param name="values"></param>
/// <param name="names"></param>
private static void DisposeSql(T entity, ref string values, ref string names)
{
EntityManage entityManage = GetEntity(entity);
foreach (var item in entityManage.keyValuePairs)
{
if (item.Key == "Id") continue;
if (entityManage.ints.Contains(item.Key))
{
values += item.Value + ",";
}
else
{
values += "'" + item.Value + "',";
}
names += item.Key + ',';
}
}
/// <summary>
/// 处理entity对象
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static EntityManage GetEntity(T entity)
{
EntityManage entityManage = new EntityManage();
entityManage.keyValuePairs = new Dictionary<string, string>();
entityManage.ints = new List<string>();
PropertyInfo[] properties = entity.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (properties.Length > 0)
{
for (int i = 0; i < properties.Length; i++)
{
if (properties[i].PropertyType.Name == "Int32")
{
entityManage.ints.Add(properties[i].Name);
}
var val = entity.GetType().GetProperty(properties[i].Name).GetValue(entity, null);
if (val != null)
{
entityManage.keyValuePairs.Add(properties[i].Name, val.ToString());
}
}
}
return entityManage;
}
/// <summary>
/// 新增
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static bool Add(T entity)
{
string values = "";
string names = "";
DisposeSql(entity, ref values, ref names);
string sql = $"insert {TableAttribute.Name} ({names.Substring(0, names.Length - 1)}) values ({values.Substring(0, values.Length - 1)})";
var result = DapperHelper.Add<T>(sql);
return Convert.ToBoolean(result);
}
}
/// <summary>
/// entity处理结构体
/// </summary>
public struct EntityManage
{
public Dictionary<string, string> keyValuePairs { get; set; }
public List<string> ints { get; set; }
}
}
UserDal继承基类IbasicDal,这样逻辑层repository实例化访问层dal就能直接访问基类的方法
创建业务层并继承于抽象层
其实抽象层和逻辑层都是基于三层架构的业务逻辑层(BLL)扩展来的,因为我们用到控制反转,所以拆分为两层,浅谈控制反转(IoC)_米碎师兄的博客-CSDN博客
同样为抽象层(以下简称IR)和逻辑层(以下简称R)创建了基类,别问,问就是省事。
1.创建IR的基类IBasicsRepository
namespace Project.TM.WCore.IRepository
{
/// <summary>
/// 接口抽象类
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IBasicsRepository
{
/// <summary>
/// 分页查
/// </summary>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <returns></returns>
dynamic? GetPageList(int index, int size);
/// <summary>
/// 列表查
/// </summary>
/// <returns></returns>
dynamic? GetList();
/// <summary>
/// 多条件查
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
dynamic? GetListByParam(Dictionary<bool, Dictionary<string, string>>? param);
/// <summary>
/// 根据id查询
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
dynamic? GetListById(int id);
/// <summary>
/// 新增
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
bool Add(dynamic? entity);
/// <summary>
/// 批增
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
bool AddList(dynamic? entityList);
/// <summary>
/// 删除
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
bool Delete(int id);
/// <summary>
/// 批删
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
bool Deletes(dynamic[]? ids);
/// <summary>
/// 编辑
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
bool Edit(dynamic? entity);
}
}
2.创建IUserRepository并继承自IBasicsRepository
一般没什么特殊业务这里是不用写代码的
using Project.TM.WCore.Model.Entity.JCXX;
namespace Project.TM.WCore.IRepository.JCXX
{
/// <summary>
/// 用户抽象
/// </summary>
public interface IUserRepository:IBasicsRepository
{
/// <summary>
/// 获取验证码
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
List<UserEntity> RegisterGet(UserEntity entity);
}
}
3.创建R层的基类BasicsRepository并继承IR层的IBasicsRepository并实现
这里为了方便不想每次都写增删改查,直接用反射根据方法名称调用dal
using Project.TM.WCore.IRepository;
using System.Reflection;
namespace Project.TM.WCore.Repository
{
/// <summary>
/// 基类
/// </summary>
public abstract class BasicsRepository: IBasicsRepository
{
/// <summary>
/// 参数
/// </summary>
public Dictionary<bool, Dictionary<string, string>>? param = null;
/// <summary>
/// 当前操作dal对象
/// </summary>
public dynamic? thisDal = null;
/// <summary>
/// 新增
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public virtual bool Add(dynamic? entity)
{
var thisDalType = thisDal?.GetType();
MethodInfo method = thisDalType.GetMethod("Add");
return method.Invoke(thisDal, new object[] { entity });
}
public virtual bool AddList(dynamic? entityList)
{
throw new NotImplementedException();
}
public virtual bool Delete(int id)
{
throw new NotImplementedException();
}
public virtual bool Deletes(dynamic[]? ids)
{
throw new NotImplementedException();
}
public virtual bool Edit(dynamic? entity)
{
throw new NotImplementedException();
}
/// <summary>
/// 查询
/// </summary>
/// <returns></returns>
public virtual dynamic GetList()
{
var thisDalType = thisDal?.GetType();
MethodInfo method = thisDalType.GetMethod("GetList");
return method.Invoke(thisDal, new object[] { });
}
public virtual dynamic GetListById(int id)
{
throw new NotImplementedException();
}
/// <summary>
/// 条件查询
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public virtual dynamic GetListByParam(Dictionary<bool, Dictionary<string, string>>? param)
{
var thisDalType = thisDal?.GetType();
MethodInfo method = thisDalType.GetMethod("GetListByParam");
return method.Invoke(thisDal, new object[] { param });
}
public virtual dynamic GetPageList(int index, int size)
{
throw new NotImplementedException();
}
}
}
4.创建UserRepository并继承自BasicsRepository和IUserRepository
一样没什么特殊业务这里也不用写代码,但是构造函数要初始化一下dal
using Project.TM.WCore.DAL.JCXX;
using Project.TM.WCore.IRepository.JCXX;
using Project.TM.WCore.Model.Entity.JCXX;
namespace Project.TM.WCore.Repository.JCXX
{
/// <summary>
/// 用户实现
/// </summary>
public class UserRepository : BasicsRepository,IUserRepository
{
/// <summary>
/// 构造函数
/// </summary>
public UserRepository()
{
thisDal = new UserDal();
}
/// <summary>
/// 注册获取信息
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public List<UserEntity> RegisterGet(UserEntity entity)
{
return null;
}
}
}
创建Api控制器方法
1.同样创建一个基类BasicsController 和控制器LoginController
2.基类存放一些公用的变量和方法
using Microsoft.AspNetCore.Mvc;
using Project.TM.WCore.IRepository.JCXX;
using System.Reflection;
namespace Project.TM.WCore.API.Controllers
{
/// <summary>
/// Controller基类
/// </summary>
[ApiController]
[Route("api/[controller]/[action]")]
public abstract class BasicsController: ControllerBase
{
//方案1:每个repository都在这里初始化,然后分布到对应的控制器去调用
/// <summary>
/// 用户信息
/// </summary>
public IUserRepository? _userRepository = null;
//方案2:定义一个动态的存放repository的对象,每次调用接口给这个repository赋值
/// <summary>
/// 当前控制器操作对象
/// </summary>
public dynamic? thisRepostory = null;
public object[]? methodParam;
/// <summary>
/// 必选参数
/// </summary>
public Dictionary<string, string>? mustParam = null;
/// <summary>
/// 可选参数
/// </summary>
public Dictionary<string, string>? minorParam = null;
/// <summary>
/// 参数汇总
/// </summary>
public Dictionary<bool, Dictionary<string, string>>? param = null;
/// <summary>
/// 多条件查询
/// </summary>
/// <param name="mustParam">必选条件</param>
/// <param name="minorParam">可选条件</param>
/// <returns></returns>
[ApiExplorerSettings(IgnoreApi = true)]
public virtual dynamic GetListByParam(Dictionary<bool, Dictionary<string, string>> param)
{
// 获取 MyClass 类型
var thisRepostoryType = thisRepostory?.GetType();
MethodInfo method = thisRepostoryType.GetMethod("GetListByParam");
return method.Invoke(thisRepostory, new object[] { param });
}
/// <summary>
/// 动态方法操作
/// </summary>
/// <param name="MethodName"></param>
/// <returns></returns>
[ApiExplorerSettings(IgnoreApi = true)]
public dynamic Method(string? MethodName)
{
var thisRepostoryType = thisRepostory?.GetType();
MethodInfo method = thisRepostoryType.GetMethod(MethodName);
var methodResult = method.Invoke(thisRepostory, methodParam);
methodParam = new object[] { };//清空参数
return methodResult;
}
}
}
3.登录注册方法创建
这块代码有点多,主要为了实现登录验证和Jwt验证JWT是什么_IBLiplus的博客-CSDN博客
1.依赖注入配置
1.nuget:
2.program 配置
//Autofac依赖注入
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
//注册依赖
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
containerBuilder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>();
//在这里写注入代码
containerBuilder.RegisterType<UserRepository>().As<IUserRepository>();//用户
containerBuilder.RegisterType<MenuRepository>().As<IMenuRepository>();//菜单
});
2.Jwt配置
1.nuget
2.创建jwt实体
namespace Project.TM.WCore.Model.Entity.PubLic
{
public class JwtSettings
{
/// <summary>
/// 发行人
/// </summary>
public string? Issuer { get; set; }
/// <summary>
/// 受众人
/// </summary>
public string? Audience { get; set; }
/// <summary>
/// 加密用的key
/// </summary>
public string? SecurityKey { get ; set; }
/// <summary>
/// 时间
/// </summary>
public double Expires { get; set; }
}
}
2.program配置
//jwt配置
var jwtSection = configuration.GetSection("JwtConfig");
JwtSettings jwtSettings = jwtSection.Get<JwtSettings>();
//添加JWT服务
builder.Services.Configure<JwtSettings>(jwtSection);
JwtSettings settings = new JwtSettings();
builder.Configuration.Bind("JwtConfig", settings);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
//验证发行人
ValidateIssuer = true,
ValidIssuer = jwtSettings?.Issuer,
验证受众人
ValidateAudience = true,
ValidAudience = jwtSettings?.Audience, // 受众
//验证token声明周期
ValidateLifetime = true,
//验证签名
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings?.SecurityKey)),// 密钥
// 是否要求Token的Claims中必须包含Expires
RequireExpirationTime = true,
// 允许服务器时间偏移量(默认300秒)
// 即我们配置的过期时间加上这个允许偏移的时间值,才是真正过期的时间(过期时间 +偏移值)
// 也可以设置为0,ClockSkew = TimeSpan.Zero
ClockSkew = TimeSpan.FromSeconds(jwtSettings.Expires)
};
//鉴权验证
options.Events = new JwtBearerEvents()
{
OnChallenge = context =>
{
context.HandleResponse();
context.Response.StatusCode = StatusCodes.Status200OK;
context.Response.ContentType = "application/json; charset=utf-8";
string resp = JsonSerializer.Serialize(new { code = 401, message = "身份证认证失败" });
context.Response.WriteAsync(resp);
return Task.FromResult(0);
},
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenException))
{
//JWT Token超时
context.Response.Headers.Add("act", "expired");
}
return Task.CompletedTask;
}
};
});
3.编写控制器方法
using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using Project.TM.WCore.Cache;
using Project.TM.WCore.Common;
using Project.TM.WCore.IRepository.JCXX;
using Project.TM.WCore.Model.Entity.JCXX;
using Project.TM.WCore.Model.Entity.PubLic;
using Project.TM.WCore.Model.Enum;
using Project.TM.WCore.Model.Response;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
namespace Project.TM.WCore.API.Controllers.JCXX
{
/// <summary>
/// 登录控制器
/// </summary>
[ApiController]
[Route("api/[controller]/[action]")]
public class LoginController : BasicsController
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="userRepository"></param>
public LoginController(IUserRepository userRepository)
{
if (_userRepository != null) return;
_userRepository = userRepository;
thisRepostory = userRepository;
}
/// <summary>
/// 登录 允许匿名
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
[HttpPost]
public IActionResult Login([FromBody] UserEntity entity)
{
JsonResult jsonResult;//返回结果
if (string.IsNullOrWhiteSpace(entity.UserName) || string.IsNullOrWhiteSpace(entity.Password))
{
jsonResult = new JsonResult(new ReturnResponse { Code = ReturnCode.Fail, Message = "用户名或密码错误", Data = new UserResponse() });
return jsonResult;
}
//查询用户信息
minorParam = new Dictionary<string, string>
{
{"UserName",entity.UserName },
{"Name",entity.UserName },
{"Contact",entity.UserName },
{"Email",entity.UserName }
};
param = new Dictionary<bool, Dictionary<string, string>>
{
{ true,mustParam},
{ false,minorParam}
};
//var user = _userRepository.GetListByParam(null, minorParam);
var user = GetListByParam(param);
if (user == null || user?.Count <= 0)
{
jsonResult = new JsonResult(new ReturnResponse { Code = ReturnCode.Fail, Message = "没有当前用户信息,请检查", Data = new UserResponse() });
return jsonResult;
}
entity.Password = MD5Helper.Md5Encrypt32(entity.Password);//密码加密
//必选参数
mustParam = new Dictionary<string, string>
{
{ "Password",entity.Password}
};
param = new Dictionary<bool, Dictionary<string, string>>
{
{ true,mustParam},
{ false,minorParam}
};
var userEntity = (GetListByParam(param) as List<UserEntity>)?.FirstOrDefault();
if (userEntity == null)
{
jsonResult = new JsonResult(new ReturnResponse { Code = ReturnCode.Fail, Message = "用户名或者密码错误" });
return jsonResult;
}
try
{
var token = GetTokenNew(userEntity);
if (string.IsNullOrWhiteSpace(token))
{
jsonResult = new JsonResult(new ReturnResponse { Code = ReturnCode.Fail, Message = "Jwt Token生成失败" });
return jsonResult;
}
else
{
//存入全局变量
PublicObject.LoginId = userEntity.Id;
PublicObject.LoginName = userEntity.Name;
PublicObject.LoginUserName = userEntity.UserName;
PublicObject.LoginDepartment = userEntity.Department;
PublicObject.LoginContact = userEntity.Contact;
jsonResult = new JsonResult(new ReturnResponse
{
Code = ReturnCode.Created,
Data = new UserResponse
{
Token = token,
UserName = entity.UserName,
Uuid = userEntity.Id,
info = new Info
{
avatar = userEntity.Avatar,
name = userEntity.Name
}
}
});
}
return jsonResult;
}
catch (Exception ex)
{
return new JsonResult(new ReturnResponse { Code = ReturnCode.InternalServerError, Message = ex.Message });
}
}
/// <summary>
/// 注册
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
[HttpPost]
public IActionResult Register(UserEntity entity)
{
List<UserEntity> userEntity = _userRepository.RegisterGet(entity);
if (userEntity.Count > 0)
{
return new JsonResult(new ReturnResponse { Code = ReturnCode.OK, Message = "当前手机号或邮箱已注册过,请更换" });
}
UserEntity newUserEntity = new UserEntity()
{
UserName = entity.Contact,
Password = MD5Helper.Md5Encrypt32(entity.Password),
Email = entity.Email,
Contact = entity.Contact,
Creator = "注册",
Createtime = DateTime.Now,
Updatetime = DateTime.Now,
Isuse = true,
Sex = true
};
bool result = _userRepository.Add(newUserEntity);
return new JsonResult(new ReturnResponse { Code = ReturnCode.Created, Data = result });
}
/// <summary>
/// 发送验证码
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
[HttpPost]
public IActionResult SendCode(UserEntity entity)
{
List<UserEntity> userEntity = _userRepository.RegisterGet(entity);
if (userEntity.Count > 0)
{
return new JsonResult(new ReturnResponse { Code = ReturnCode.OK, Message = "当前手机号或邮箱已注册过,请更换" });
}
int returncode = PhoneNo(entity.Contact);
if (returncode != 0)
{
return new JsonResult(new ReturnResponse
{
Code = ReturnCode.Created,
Data = new UserResponse
{
Captcha = returncode
}
});
}
return new JsonResult(new ReturnResponse { Code = ReturnCode.Fail, Message = "验证码发送失败" });
}
/// <summary>
/// 实现发送验证码
/// </summary>
/// <param name="phoneno">手机号</param>
/// <returns>验证码</returns>
[ApiExplorerSettings(IgnoreApi = true)]
public static int PhoneNo(string phoneno)
{
string account = "C38910992";//查看用户名 登录用户中心->验证码通知短信>产品总览->API接口信息->APIID
string password = "fbb1c64c267100f784276d9101bdce48"; //查看密码 登录用户中心->验证码通知短信>产品总览->API接口信息->APIKEY
string mobile = phoneno;
Random rad = new Random();
int mobile_code = rad.Next(1000, 10000);
string content = "您的验证码是:" + mobile_code + " 。请不要把验证码泄露给其他人。";
string postStrTpl = "account={0}&password={1}&mobile={2}&content={3}";
UTF8Encoding encoding = new UTF8Encoding();
byte[] postData = encoding.GetBytes(string.Format(postStrTpl, account, password, mobile, content));
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(AuthCodeProvider.GetPostUrl());
myRequest.Method = "POST";
myRequest.ContentType = "application/x-www-form-urlencoded";
myRequest.ContentLength = postData.Length;
Stream newStream = myRequest.GetRequestStream();
newStream.Write(postData, 0, postData.Length);
newStream.Flush();
newStream.Close();
HttpWebResponse myResponse = (HttpWebResponse)myRequest.GetResponse();
if (myResponse.StatusCode == HttpStatusCode.OK)
{
StreamReader reader = new StreamReader(myResponse.GetResponseStream(), Encoding.UTF8);
//code状态返回值;msg查询结果描述
string res = reader.ReadToEnd();
int len1 = res.IndexOf("</code>");
int len2 = res.IndexOf("<code>");
string code = res.Substring((len2 + 6), (len1 - len2 - 6));
int len3 = res.IndexOf("</msg>");
int len4 = res.IndexOf("<msg>");
string msg = res.Substring((len4 + 5), (len3 - len4 - 5));
if (msg == "提交成功")
{
return mobile_code;
}
else
{
return 0;
}
}
else
{
return 0;
}
}
/// <summary>
/// 获取JWT.Token
/// </summary>
/// <returns></returns>
[ApiExplorerSettings(IgnoreApi = true)]
private string GetToken(UserEntity entity)
{
AuthInfo authInfo = new AuthInfo()
{
UserName = entity.UserName,
IsAdmin = true,
ExpirationTime = 600
};
//密钥
const string secret = "To Live is to change the world";
//secret需要加密
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(authInfo, secret);
//验证token是否为空
if (string.IsNullOrWhiteSpace(token)) return "";
return token;
}
[ApiExplorerSettings(IgnoreApi = true)]
private string GetTokenNew(UserEntity entity)
{
var userData = new
{
Name = entity.UserName,
CreatedTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
};
// 设置序列化首字母小写
JsonSerializerOptions jso = new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
string json = JsonSerializer.Serialize(userData, jso);
var claims = new Claim[]
{
// 注意不能放入密码等敏感信息
new Claim("user", json),
// 也可以增加一些JWT预定义的claim
new Claim(ClaimTypes.Name, "admin")
};
var jwtSet = SettingProvider.GetJwtSetting();
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSet?.SecurityKey));
var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
DateTime now = DateTime.Now;
DateTime exp = DateTime.Now.AddSeconds(jwtSet.Expires);
var token = new JwtSecurityToken(
issuer: jwtSet.Issuer, // 发行人
audience: jwtSet.Audience,//受众人
//claims: claims, // 具体声明信息,注意不能放入敏感信息,这里客户端可以解密查看
notBefore: now, // 有效期开始时间
expires: exp, // 有效期结束时间
signingCredentials: signingCredentials);
string str = new JwtSecurityTokenHandler().WriteToken(token);
return str;
}
}
}
完成测试
成功
到这里框架的创建就完成了, 本人菜鸡,大佬轻喷,班门弄斧,还请海涵
版权归原作者 你品 所有, 如有侵权,请联系我们删除。