.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

相关文章

mybatis的整体架构

mybatis的整体架构分为三层: 1.基础支持层 该层包括:数据源模块、事务管理模块、缓存模块、Binding模块、反射模块、类型转换模块、日志模块、资源加载模块、解析器模块 2.核心处理层 该层包括:配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件 3.接口层 该层包括:SqlSession 基础支持层 该层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。

百度/小米/滴滴/京东,中台架构比较

小米中台建设实践 01 小米的三大中台建设:业务+数据+技术 业务中台--从业务说起 在中台建设中,需要规范化的服务接口、一致整合化的数据、容器化的技术组件以及弹性的基础设施。并结合业务情况,判定是否真的需要中台。 小米参考了业界优秀的案例包括移动中台、数据中台、业务中台、技术中台等,再结合其业务发展历程及业务现状,整理了中台架构的核心方法论,一是企业如何共享服务,二是如何为业务提供便利。

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

搭建Kafka+zookeeper集群调度

前言 硬件环境 172.18.0.5        kafkazk1        Kafka+zookeeper                Kafka Broker集群 172.18.0.6        kafkazk2        Kafka+zookeeper                Kafka Broker集群 172.18.0.7        kafkazk3

uva 10387 Billiard(简单几何)

题意是一个球从矩形的中点出发,告诉你小球与矩形两条边的碰撞次数与小球回到原点的时间,求小球出发时的角度和小球的速度。 简单的几何问题,小球每与竖边碰撞一次,向右扩展一个相同的矩形;每与横边碰撞一次,向上扩展一个相同的矩形。 可以发现,扩展矩形的路径和在当前矩形中的每一段路径相同,当小球回到出发点时,一条直线的路径刚好经过最后一个扩展矩形的中心点。 最后扩展的路径和横边竖边恰好组成一个直

poj 1113 凸包+简单几何计算

题意: 给N个平面上的点,现在要在离点外L米处建城墙,使得城墙把所有点都包含进去且城墙的长度最短。 解析: 韬哥出的某次训练赛上A出的第一道计算几何,算是大水题吧。 用convexhull算法把凸包求出来,然后加加减减就A了。 计算见下图: 好久没玩画图了啊好开心。 代码: #include <iostream>#include <cstdio>#inclu

uva 10130 简单背包

题意: 背包和 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <queue>#include <map>

【IPV6从入门到起飞】5-1 IPV6+Home Assistant(搭建基本环境)

【IPV6从入门到起飞】5-1 IPV6+Home Assistant #搭建基本环境 1 背景2 docker下载 hass3 创建容器4 浏览器访问 hass5 手机APP远程访问hass6 更多玩法 1 背景 既然电脑可以IPV6入站,手机流量可以访问IPV6网络的服务,为什么不在电脑搭建Home Assistant(hass),来控制你的设备呢?@智能家居 @万物互联