.Net5 框架搭建(一):简单三层架构+Freesql+Autofac

2023-11-22 06:48

本文主要是介绍.Net5 框架搭建(一):简单三层架构+Freesql+Autofac,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

由于业务需要,希望搭建一套基于Freesql(ORM)的简单易懂三层架构,按照目前主流的开发模式都是仓储层+三层架构在开发,本框架去除仓储层,有些命名也比较通俗易懂,用于学习就好,前面大部分都是在描述一些基本的三层架构搭建,想看Autofac(第三方依赖注入容器)怎么使用的可以直接拉到最下面。

正文

三层架构的大致流程图:
在这里插入图片描述
项目框架大概组成截图:
在这里插入图片描述

  • 0.Core:通用层
  • 1.Model:实体层
  • 2.DAL:数据访问层
  • 3.BLL:业务逻辑层
  • 4.WebApi:表示层

0.Core(通用层)

这层我存放一些通用的帮助类,包含封装的访问数据库上下文基类

1、直接Nuget添加这些包,或者直接项目编辑新增

  <ItemGroup><PackageReference Include="FreeSql" Version="2.5.100" /><PackageReference Include="FreeSql.Provider.MySql" Version="2.5.100" /><PackageReference Include="FreeSql.Provider.Oracle" Version="2.5.100" /><PackageReference Include="FreeSql.Provider.Sqlite" Version="2.5.100" /><PackageReference Include="FreeSql.Provider.SqlServer" Version="2.5.100" /><PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" /><PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" /><PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" /></ItemGroup>

2、基于IFreeSql创建一个访问数据库基类
FSqlBase.cs

using DXDF.Core.Common.Helper;
using FreeSql;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace DXDF.Core.Common
{public sealed class FBase{private static string connStr = Appsettings.app(new string[] { "connectionString" }).ObjToString();private static int dbType = Appsettings.app(new string[] { "dbType" }).ObjToInt();//private static string ConnStr = ConfigurationManager.ConnectionStrings["FreeSql:Default"].ConnectionString;public static readonly IFreeSql _fsql = new FreeSqlBuilder().UseConnectionString((DataType)dbType, connStr)//.UseSyncStructureToUpper(true)//.UseNameConvert(FreeSql.Internal.NameConvertType.ToUpper).Build();//public FBase()//{//    _fsql.Aop.CurdBefore += (s, e) =>//    {//        //System.Console.WriteLine(e.Sql);//        log.Debug(string.Format("\r\n---{0}---\r\n{1}", System.DateTime.Now,e.Sql));//    };//}}public abstract class FSqlBase{public IFreeSql fsql{get { return FBase._fsql; }}public FSqlBase(){fsql.Aop.CurdBefore += (s, e) =>{System.Console.WriteLine(e.Sql);};}}
}

3、封装一个操作appsettings.json的帮助类
Appsettings.cs

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace DXDF.Core.Common.Helper
{/// <summary>/// appsettings.json操作类/// </summary>public class Appsettings{static IConfiguration Configuration { get; set; }static string contentPath { get; set; }public Appsettings(string contentPath){string Path = "appsettings.json";//如果你把配置文件 是 根据环境变量来分开了,可以这样写//Path = $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json";Configuration = new ConfigurationBuilder().SetBasePath(contentPath).Add(new JsonConfigurationSource { Path = Path, Optional = false, ReloadOnChange = true })//这样的话,可以直接读目录里的json文件,而不是 bin 文件夹下的,所以不用修改复制属性.Build();}public Appsettings(IConfiguration configuration){Configuration = configuration;}/// <summary>/// 封装要操作的字符/// </summary>/// <param name="sections">节点配置</param>/// <returns></returns>public static string app(params string[] sections){try{if (sections.Any()){return Configuration[string.Join(":", sections)];}}catch (Exception) { }return "";}/// <summary>/// 递归获取配置信息数组/// </summary>/// <typeparam name="T"></typeparam>/// <param name="sections"></param>/// <returns></returns>public static List<T> app<T>(params string[] sections){List<T> list = new List<T>();// 引用 Microsoft.Extensions.Configuration.Binder 包Configuration.Bind(string.Join(":", sections), list);return list;}}
}

4、数据类型转换的帮助类
UtilConvert.cs

using System;
namespace DXDF.Core.Common.Helper
{/// <summary>/// 转换帮助类/// </summary>public static class UtilConvert{/// <summary>/// /// </summary>/// <param name="thisValue"></param>/// <returns></returns>public static int ObjToInt(this object thisValue){int reval = 0;if (thisValue == null) return 0;if (thisValue != null && thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out reval)){return reval;}return reval;}/// <summary>/// /// </summary>/// <param name="thisValue"></param>/// <param name="errorValue"></param>/// <returns></returns>public static int ObjToInt(this object thisValue, int errorValue){int reval = 0;if (thisValue != null && thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out reval)){return reval;}return errorValue;}/// <summary>/// /// </summary>/// <param name="thisValue"></param>/// <returns></returns>public static double ObjToMoney(this object thisValue){double reval = 0;if (thisValue != null && thisValue != DBNull.Value && double.TryParse(thisValue.ToString(), out reval)){return reval;}return 0;}/// <summary>/// /// </summary>/// <param name="thisValue"></param>/// <param name="errorValue"></param>/// <returns></returns>public static double ObjToMoney(this object thisValue, double errorValue){double reval = 0;if (thisValue != null && thisValue != DBNull.Value && double.TryParse(thisValue.ToString(), out reval)){return reval;}return errorValue;}/// <summary>/// /// </summary>/// <param name="thisValue"></param>/// <returns></returns>public static string ObjToString(this object thisValue){if (thisValue != null) return thisValue.ToString().Trim();return "";}/// <summary>/// /// </summary>/// <param name="thisValue"></param>/// <returns></returns>public static bool IsNotEmptyOrNull(this object thisValue){return ObjToString(thisValue) != "" && ObjToString(thisValue) != "undefined" && ObjToString(thisValue) != "null";}/// <summary>/// /// </summary>/// <param name="thisValue"></param>/// <param name="errorValue"></param>/// <returns></returns>public static string ObjToString(this object thisValue, string errorValue){if (thisValue != null) return thisValue.ToString().Trim();return errorValue;}/// <summary>/// /// </summary>/// <param name="thisValue"></param>/// <returns></returns>public static Decimal ObjToDecimal(this object thisValue){Decimal reval = 0;if (thisValue != null && thisValue != DBNull.Value && decimal.TryParse(thisValue.ToString(), out reval)){return reval;}return 0;}/// <summary>/// /// </summary>/// <param name="thisValue"></param>/// <param name="errorValue"></param>/// <returns></returns>public static Decimal ObjToDecimal(this object thisValue, decimal errorValue){Decimal reval = 0;if (thisValue != null && thisValue != DBNull.Value && decimal.TryParse(thisValue.ToString(), out reval)){return reval;}return errorValue;}/// <summary>/// /// </summary>/// <param name="thisValue"></param>/// <returns></returns>public static DateTime ObjToDate(this object thisValue){DateTime reval = DateTime.MinValue;if (thisValue != null && thisValue != DBNull.Value && DateTime.TryParse(thisValue.ToString(), out reval)){reval = Convert.ToDateTime(thisValue);}return reval;}/// <summary>/// /// </summary>/// <param name="thisValue"></param>/// <param name="errorValue"></param>/// <returns></returns>public static DateTime ObjToDate(this object thisValue, DateTime errorValue){DateTime reval = DateTime.MinValue;if (thisValue != null && thisValue != DBNull.Value && DateTime.TryParse(thisValue.ToString(), out reval)){return reval;}return errorValue;}/// <summary>/// /// </summary>/// <param name="thisValue"></param>/// <returns></returns>public static bool ObjToBool(this object thisValue){bool reval = false;if (thisValue != null && thisValue != DBNull.Value && bool.TryParse(thisValue.ToString(), out reval)){return reval;}return reval;}/// <summary>/// 获取当前时间的时间戳/// </summary>/// <param name="thisValue"></param>/// <returns></returns>public static string DateToTimeStamp(this DateTime thisValue){TimeSpan ts = thisValue - new DateTime(1970, 1, 1, 0, 0, 0, 0);return Convert.ToInt64(ts.TotalSeconds).ToString();}}
}

1.Model(实体层)

实体层,我主要在存放一些数据实体表、枚举类、自定义接收返回类
1、Nuget包

  <ItemGroup><PackageReference Include="FreeSql" Version="2.5.100" /><PackageReference Include="Newtonsoft.Json" Version="13.0.1" /></ItemGroup>

2、简单写一个用户表实体

using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace DXDF.Core.Model.Model
{/// <summary>///  用户表/// </summary>[JsonObject(MemberSerialization.OptIn), Table(Name = "st_test")]public class SystemUser{/// <summary>/// 编号/// </summary>[JsonProperty]public long? Id { get; set; }/// <summary>/// 姓名/// </summary>[JsonProperty, Column(DbType = "varchar(50)")]public string Name { get; set; }/// <summary>/// 账号/// </summary>[JsonProperty, Column(DbType = "varchar(50)")]public string UserName { get; set; }/// <summary>/// 密码/// </summary>[JsonProperty, Column(DbType = "varchar(50)")]public string Password { get; set; }/// <summary>/// 删除标志/// </summary>[JsonProperty]public int? DeleteMark { get; set; }/// <summary>/// 有效标志/// </summary>[JsonProperty]public int? EnabledMark { get; set; }/// <summary>/// 创建时间/// </summary>[JsonProperty]public DateTime? CreateTime { get; set; }/// <summary>/// 编辑时间/// </summary>[JsonProperty]public DateTime? ModifyTime { get; set; }}
}

3、状态枚举类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace DXDF.Core.Model.Enum
{/// <summary>/// 状态枚举类/// </summary>public enum DXCode : short{Unknow = -1,/// <summary>/// 成功/// </summary>Success = 0,/// <summary>/// 失败/// </summary>Failure = -128,/// <summary>/// 错误请求/// </summary>BadRequest = -256,/ <summary>/ 执行失败/ </summary>//ActionExecutingFailure = -10002,/ <summary>/ 意外失败/ </summary>//UnexpectedFailure = -10003,/// <summary>/// 非法请求(登陆超时)/// </summary>Unauthorized = -16,}
}

4、自定义消息返回类

using DXDF.Core.Model.Enum;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace DXDF.Core.Model.ViewModels
{/// <summary>/// 消息返回类/// </summary>public class DXResult{/// <summary>/// 状态  /// </summary>public DXCode code { get; set; } = DXCode.Unknow;/// <summary>/// 描述/// </summary>public string msg { get; set; }/// <summary>/// 具体数据 /// </summary>public dynamic data { get; set; }/// <summary>/// 数据影响行数/// </summary>public long? count { get; set; }}
}

2.DAL(数据访问层)

这层主要与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库。
1、Nuget包

  <ItemGroup><PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /></ItemGroup>

2、SystemUserDAL.cs

using DXDF.Core.Common;
using DXDF.Core.Model;
using DXDF.Core.Model.Enum;
using DXDF.Core.Model.Model;
using DXDF.Core.Model.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;namespace DXDF.Core.DAL
{public class SystemUserDAL : FSqlBase{#region CURD(增删查改)public Task<List<SystemUser>> GetList(int pageSize, int pageIndex, ref long count, Expression<Func<SystemUser, bool>> where = null, string order = null){return fsql.Queryable<SystemUser>().WhereIf(where != null, where).Count(out count).Page(pageIndex, pageSize).OrderBy(!string.IsNullOrWhiteSpace(order), order).ToListAsync();}public Task<SystemUser> GetById(long id){return fsql.Queryable<SystemUser>().Where(it => it.Id == id).FirstAsync();}public Task<int> UpdateOrInsert(SystemUser item){if (item?.Id > 0){return fsql.Update<SystemUser>().SetSource(item).IgnoreColumns(it => new { it.CreateTime }).ExecuteAffrowsAsync();}elsereturn fsql.Insert(item).ExecuteAffrowsAsync();}public Task<int> Delete(long id){return fsql.Delete<SystemUser>().Where(it => it.Id == id).ExecuteAffrowsAsync();}#endregion}
}

3.BLL(业务逻辑层)

UI层和DAL层之间的桥梁。实现业务逻辑。业务逻辑具体包含:验证、计算、业务规则等等。

1、SystemUserBLL.cs

using DXDF.Core.DAL;
using DXDF.Core.Model;
using DXDF.Core.Model.Enum;
using DXDF.Core.Model.Model;
using DXDF.Core.Model.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace DXDF.Core.BLL
{public class SystemUserBLL : ISystemUserBLL{private readonly SystemUserDAL _dal;/// <summary>/// 构造函数注入/// </summary>/// <param name="dal"></param>public SystemUserBLL(SystemUserDAL dal){_dal = dal;}/// <summary>/// 添加用户/// </summary>/// <param name="model"></param>/// <returns></returns>public async Task<DXResult> AddUsers(SystemUser model){DXResult dXResult = new DXResult();int i = await _dal.UpdateOrInsert(model);if (i > 0){dXResult.code = DXCode.Success;}elsedXResult.code = DXCode.Failure;return dXResult;}/// <summary>/// 获取用户/// </summary>/// <param name="id"></param>/// <returns></returns>public async Task<SystemUser> GetUser(long id){SystemUser dXResult = await _dal.GetById(id);return dXResult;}}
}

2、ISystemUserBLL.cs

using DXDF.Core.Model.Model;
using DXDF.Core.Model.ViewModels;
using System.Threading.Tasks;namespace DXDF.Core.BLL
{public partial interface ISystemUserBLL{Task<DXResult> AddUsers(SystemUser model);Task<SystemUser> GetUser(long id);DXResult Update(SystemUser item);}
}

4.WebApi(表示层)

这一层主要就是实现前后端交互的数据接口操作
本层涉及AutoFac,也会讲解为什么引用他,而不用net core自带的依赖注入
1、Nuget包

  <ItemGroup><PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.1.0" /><PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" /></ItemGroup>

2、新建一个用户控制器类

using DXDF.Core.BLL;
using DXDF.Core.Model.Model;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860namespace DXDF.Core.Controllers
{[Route("api/[controller]")][ApiController]public class UserController : ControllerBase{private readonly ISystemUserBLL _systemUserBLL;/// <summary>/// 构造函数注入/// </summary>public UserController(ISystemUserBLL systemUserBLL){_systemUserBLL = systemUserBLL;}[HttpPost("addUser")]public async Task<dynamic> Add(SystemUser user){var obj = await _systemUserBLL.AddUsers(user);return obj;}[HttpGet("getUser")]public async Task<dynamic> Get(long id){var obj = await _systemUserBLL.GetUser(id);return obj;}[HttpPost("updateUser")]public dynamic UpdateUser(SystemUser user){var obj = _systemUserBLL.Update(user);return obj;}}
}

3、配置文件appsettings.json

{"Logging": {"LogLevel": {"Default": "Information","Microsoft": "Warning","Microsoft.Hosting.Lifetime": "Information"}},"AllowedHosts": "*",//连接字符串//MySql "Server=localhost; Port=3306; Database=admindb; Uid=root; Pwd=pwd; Charset=utf8mb4;"//SqlServer "Data Source=.;Integrated Security=True;Initial Catalog=admindb;Pooling=true;Min Pool Size=1"//PostgreSQL "Host=localhost;Port=5432;Username=postgres;Password=; Database=admindb;Pooling=true;Minimum Pool Size=1"//Sqlite "Data Source=|DataDirectory|\\admindb.db; Pooling=true;Min Pool Size=1""connectionString": "Data Source=localhost;Initial Catalog=TestDb;Persist Security Info=True;User ID=sa1;Password=123456;Pooling=true;Min Pool Size=1;Connection LifeTime=20",//数据库类型 MySql = 0, SqlServer = 1, PostgreSQL = 2, Oracle = 3, Sqlite = 4, OdbcOracle = 5, OdbcSqlServer = 6, OdbcMySql = 7, OdbcPostgreSQL = 8, Odbc = 9, OdbcDameng = 10, MsAccess = 11, Dameng = 12, OdbcKingbaseES = 13, ShenTong = 14, KingbaseES = 15, Firebird = 16"dbType": "1"
}

这时候我们还是没办法使用这些接口的,需要依赖注入这些服务,说到服务的注入,就不得不提到服务的生命周期,netcore 一共提供三种注入的生命周期,分别是Transient(暂时)Scoped(作用域)Singleton(单例)。

常用两种依赖注入的方法,一种是原生DI-NetCore,另外一种是第三方的依赖注入容器DI-AutoFac。

DI-NetCore

Startup.cs

        public void ConfigureServices(IServiceCollection services){            //单例注入services.AddSingleton(new Appsettings(Configuration));services.AddSingleton<ISystemUserBLL, SystemUserBLL>();services.AddSingleton(new SystemUserDAL());services.AddControllers();services.AddSwaggerGen(c =>{c.SwaggerDoc("v1", new OpenApiInfo { Title = "DXDF.Core", Version = "v1" });});}

运行api项目,开始测试
在这里插入图片描述
数据库查询一下,成功
在这里插入图片描述

DI-AutoFac

问:为什么使用DI-AutoFac?
答:原生的服务注入方式没办法达到批量,比如写了两个服务接口,那就得分别注入两个。

            services.AddSingleton<IUserBLL, UserBLL>();services.AddSingleton<IDictABLL, IDictABLL>();....

这样子明显会照成很多重复的功能量跟代码冗余,所以这时候我们需要引用一个批量帮我们操作的工具,那就是DI-AutoFac,又称IOC(控制反转容器)

具体用法

  • 修改Program.cs
    public class Program{public static void Main(string[] args){CreateHostBuilder(args).Build().Run();}public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args).UseServiceProviderFactory(new AutofacServiceProviderFactory())//替换默认容器.ConfigureWebHostDefaults(webBuilder =>{webBuilder.UseStartup<Startup>();});}
  • 修改Startup.cs
    新增方法ConfigureContainer
        public void ConfigureContainer(ContainerBuilder builder){#region AutoFac IOC容器,实现批量依赖注入的容器try{#region SingleInstance//无接口注入单例var assemblyDAL = Assembly.Load("DXDF.Core.DAL");builder.RegisterAssemblyTypes(assemblyDAL).SingleInstance();#endregion#region Servicevar assemblyServices = Assembly.Load("DXDF.Core.BLL");builder.RegisterAssemblyTypes(assemblyServices).AsImplementedInterfaces().InstancePerLifetimeScope().PropertiesAutowired();// 属性注入#endregion}catch (Exception ex){throw new Exception(ex.Message + "\n" + ex.InnerException);}#endregion}

完整代码已上传码云:https://gitee.com/shao-jiayong/cuo-ding

这篇关于.Net5 框架搭建(一):简单三层架构+Freesql+Autofac的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/408280

相关文章

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

redis群集简单部署过程

《redis群集简单部署过程》文章介绍了Redis,一个高性能的键值存储系统,其支持多种数据结构和命令,它还讨论了Redis的服务器端架构、数据存储和获取、协议和命令、高可用性方案、缓存机制以及监控和... 目录Redis介绍1. 基本概念2. 服务器端3. 存储和获取数据4. 协议和命令5. 高可用性6.

修改若依框架Token的过期时间问题

《修改若依框架Token的过期时间问题》本文介绍了如何修改若依框架中Token的过期时间,通过修改`application.yml`文件中的配置来实现,默认单位为分钟,希望此经验对大家有所帮助,也欢迎... 目录修改若依框架Token的过期时间修改Token的过期时间关闭Token的过期时js间总结修改若依

JAVA调用Deepseek的api完成基本对话简单代码示例

《JAVA调用Deepseek的api完成基本对话简单代码示例》:本文主要介绍JAVA调用Deepseek的api完成基本对话的相关资料,文中详细讲解了如何获取DeepSeekAPI密钥、添加H... 获取API密钥首先,从DeepSeek平台获取API密钥,用于身份验证。添加HTTP客户端依赖使用Jav

本地搭建DeepSeek-R1、WebUI的完整过程及访问

《本地搭建DeepSeek-R1、WebUI的完整过程及访问》:本文主要介绍本地搭建DeepSeek-R1、WebUI的完整过程及访问的相关资料,DeepSeek-R1是一个开源的人工智能平台,主... 目录背景       搭建准备基础概念搭建过程访问对话测试总结背景       最近几年,人工智能技术

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查

微服务架构之使用RabbitMQ进行异步处理方式

《微服务架构之使用RabbitMQ进行异步处理方式》本文介绍了RabbitMQ的基本概念、异步调用处理逻辑、RabbitMQ的基本使用方法以及在SpringBoot项目中使用RabbitMQ解决高并发... 目录一.什么是RabbitMQ?二.异步调用处理逻辑:三.RabbitMQ的基本使用1.安装2.架构

5分钟获取deepseek api并搭建简易问答应用

《5分钟获取deepseekapi并搭建简易问答应用》本文主要介绍了5分钟获取deepseekapi并搭建简易问答应用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需... 目录1、获取api2、获取base_url和chat_model3、配置模型参数方法一:终端中临时将加

利用Python编写一个简单的聊天机器人

《利用Python编写一个简单的聊天机器人》这篇文章主要为大家详细介绍了如何利用Python编写一个简单的聊天机器人,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 使用 python 编写一个简单的聊天机器人可以从最基础的逻辑开始,然后逐步加入更复杂的功能。这里我们将先实现一个简单的

使用IntelliJ IDEA创建简单的Java Web项目完整步骤

《使用IntelliJIDEA创建简单的JavaWeb项目完整步骤》:本文主要介绍如何使用IntelliJIDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Se... 目录前置准备项目功能实现步骤1. 创建项目2. 配置 Tomcat3. 项目文件结构4. 创建数据库和表5.