POST
axios.post('AjaxCases', {
following: true,
success: null,
page: 1,
}).then(function (response) {
console.log(response.data)
}).catch(function (error) {
console.log(error.response.data);
});
[HttpPost]
public IActionResult AjaxCases([FromBody] AjaxCasesRequestModel req)
{
bool? following = req.following;
bool? success = req.success;
int page = req.page;
}
GET
axios.get('AjaxCase', {
params: {
int: 123,
}
}).then(function (response) {
console.log(response.data)
}).catch(function (error) {
console.log(error.response.data);
});
[HttpGet]
public IActionResult AjaxCases([FromQuery] int id)
{
}
DELETE
格式与 GET 类似
* 若服务端获取参数值为 null,可能的情况如下:
请检查相关枚举类型是否已设置 [JsonConverter] 属性,参:C# 枚举(enum)用法笔记,且传入的值不能为空字符串,可以传入 null 值,并将服务端 enum 类型改为可空。可以以 string 方式接收参数后再进行转换。
模型中属性名称不能重复(大小写不同也不行)。
布尔型属性值必须传递布尔型值,即在 JS 中,字符串 "true" 应 JSON.parse("true") 为 true 后再回传给服务端。
前提:
监控摄像头有推流功能
抖音有直播权限(粉丝数超过 1000)
一个已备案的域名
名词解释:
推流地址:由阿里云生成的接收摄像头视频流推送的地址。
播流地址:由阿里云生成可用于终端播放的视频流地址。
各环节功能解释:
摄像头:负责采集视频信息,并推送到阿里云。
阿里云视频直播服务:接收视频推流并分发到用户端(收费)。
OBS 软件:负责将视频流转化为可供直播伴侣调用的视频源。
直播伴侣:将视频源分发到短视频终端用户。
第一步:配置阿里云视频直播服务
1.1 在阿里云控制台中开通视频直播,在域名管理中添加域名。
这里需要添加两个域名,一个是推流域名,一个是播流域名。顾名思义,“推流”指从摄像头将流推送到阿里云,“播流”是用户播放视频流。
以添加域名推流域名“rtmp.xoyozo.net”和播流域名“live.xoyozo.net”为例:
然后按列表中的 CNAME 地址对域名作解析即可。
解析生效后,点击任意一个域名进行配置,将推流域名和播流域名相互绑定。成功后,可以在推流域名中的播流信息中看到播流域名,在播流域名的推流信息中也能看到推流域名。
1.2 生成推流地址/播流地址
在工具箱中点击地址生成器。
选择推流域名和播流域名,AppName 和 StreamName 任意填写,生成成功后获取以下地址:
注:鉴权串是按照鉴权规则生成的串,默认有效期 30 分钟,超出时间即中断推流。可以在域名管理中修改该值,或直接关闭 URL 鉴权。
第二步:在摄像机控制面板中配置推流域名
该步骤因摄像机的品牌和型号不同而有所差异。本文以 IP CAMERA 为例。
首先使用 IP 搜索工具在局域网上搜索摄像机,找到摄像机的 IP 地址。
在浏览器上打开,输入用户名和密码进入管理面板。
在“参数设置”-“网络设置”- RTMP Publish 中,将推流地址填入到“预定网址”中,点击“应用”。
刷新后查看“直播状态”和“直播网址”是否已生效。
若配置成功,可将播流地址在视频播放器中打开观看,或直接在 iPhone 或安卓手机中打开播流地址。
第三步:使用 OBS Studio 将直播流信号转换为虚拟摄像头信号
因抖音直播伴侣无法使用播流地址作为视频源进行直播输出,故使用 OBS 将播流信号转化为虚拟的本机摄像头信号源供直播伴侣调用。
下载安装 OBS Studio,添加一个“场景”:
然后添加一个“媒体源”:
去掉“本地文件”前面的勾,将播流地址填到“输入”框中:
确定后即可预览到直播内容:
拖动视频区域可调整输出范围。
点击“启动虚拟摄像机”。
提示:OBS 创建的虚拟摄像机默认会从物理麦克风和桌面系统音频拾音,请按需在“混音器”中配置。
第四步:OBS 使用 VLC 视频源(此步可略过)
若 OBS 直接添加媒体源不稳定,可以使用 VLC 视频源。
下载安装 VLC 播放器。
在 OBS Studio 中添加来源,选择“VLC 视频源”:
点击“播放列表”右侧的“+”,选择“添加路径 /URL”:
填写播流地址并确定。
第五步:配置抖音直播伴侣使用 OBS 的虚拟摄像机信号源
下载安装抖音直播伴侣,在任意场景中添加素材,选择“摄像头”:
摄像头选择“OBS Virtual Camera”:
点击“开始直播”:
“DatabaseFacade”未包含“ExecuteSqlCommand”的定义,并且找不到可接受第一个“DatabaseFacade”类型参数的可访问扩展方法“ExecuteSqlCommand”(是否缺少 using 指令或程序集引用?)
思路:(以 Entity Framework Core 5.0 为例)
先借用 SELETE 的 IQueryable 语句,将 SELETE 关键字替换成 DELETE,再使用 FromSqlRaw 执行。这样可准确使用真实的数据库表名和字段名。
如:
using Microsoft.EntityFrameworkCore;
string sql = db.表.Where(c => c.Date < minDate).ToQueryString();
sql = Regex.Replace(sql, @"SELECT\s.*?\sFROM", "DELETE FROM", RegexOptions.Singleline); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` AS `d` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"\sAS\s`(.*?)`", "", RegexOptions.Singleline); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"`([^`]*?)`\.`", "`", RegexOptions.Singleline); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` WHERE `Date` < @__minDate_0
db.Database.ExecuteSqlRaw(sql);
以上例子针对 MySQL,且未考虑字符串内容中包含符号 ` 的情况。实例操作中应严格防范 SQL 注入。
封装成方法:
using Microsoft.EntityFrameworkCore;
const RegexOptions ro = RegexOptions.Singleline;
/// <summary>
/// 组装一个 DELETE SQL 语句
/// </summary>
/// <typeparam name="T">数据库表实体类</typeparam>
/// <param name="queryableWithConditions">IQueryable,可带筛选条件</param>
/// <returns></returns>
public static string AssembleDeleteSql<T>(IQueryable<T> queryableWithConditions) where T : class
{
// MySQL 版
string sql = queryableWithConditions.ToQueryString(); // SET @__minDate_0 = '2020-01-14'; SELECT `d`.`Date`, `d`.`Code6` FROM `表` AS `d` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"SELECT\s.*?\sFROM", "DELETE FROM", ro); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` AS `d` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"\sAS\s`(.*?)`", "", ro); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"`([^`]*?)`\.`", "`", ro); // SET @__minDate_0 = '2020-01-14'; DELETE FROM `表` WHERE `Date` < @__minDate_0
return sql;
}
/// <summary>
/// 组装一个 UPDATE SQL 语句
/// </summary>
/// <typeparam name="T">数据库表实体类</typeparam>
/// <param name="queryableWithConditions">IQueryable,可带筛选条件</param>
/// <param name="columnsAndValues">需要更改的字段和值</param>
/// <returns></returns>
public static string AssembleUpdateSql<T>(IQueryable<T> queryableWithConditions, Dictionary<string, object> columnsAndValues) where T : class
{
// MySQL 版
string sql = queryableWithConditions.ToQueryString(); // SET @__minDate_0 = '2020-01-14'; SELECT `d`.`Date`, `d`.`Code6` FROM `表` AS `d` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"SELECT\s.*?\sFROM", "UPDATE", ro); // SET @__minDate_0 = '2020-01-14'; UPDATE `表` AS `d` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"\sAS\s`(.*?)`", "", ro); // SET @__minDate_0 = '2020-01-14'; UPDATE `表` WHERE `d`.`Date` < @__minDate_0
sql = Regex.Replace(sql, @"`([^`]*?)`\.`", "`", ro); // SET @__minDate_0 = '2020-01-14'; UPDATE `表` WHERE `Date` < @__minDate_0
string sets = string.Join(",", columnsAndValues.Select(c => $"`{c.Key.Replace("`", @"\`")}` = '{c.Value.ToString().Replace("'", @"\'")}'"));
sql = Regex.Replace(sql, @"`\s*WHERE", $"` SET {sets} WHERE", ro); // SET @__minDate_0 = '2020-01-14'; UPDATE `表` SET `field1` = 'value1', `field2` = 'value2' WHERE `Date` < @__minDate_0
return sql;
}
调用示例:
using Microsoft.EntityFrameworkCore;
// 删除记录
DateTime minDate = DateTime.Today.AddYears(-1).AddMonths(-1);
string sql = AssembleDeleteSql(db.表.Where(c => c.Date < minDate));
db.Database.ExecuteSqlRaw(sql);
// 更新记录
var sets = new Dictionary<string, object>();
foreach (var p in db.Model.FindEntityType(typeof(表)).GetProperties())
{
switch (p.Name)
{
case nameof(表.字段一): sets.Add(p.GetColumnBaseName(), 123); break;
case nameof(表.字段二): sets.Add(p.GetColumnBaseName(), 456); break;
}
}
DateTime minDate = DateTime.Today.AddYears(-1).AddMonths(-1);
string sql = AssembleUpdateSql(db.表.Where(c => c.Date < minDate), sets);
db.Database.ExecuteSqlRaw(sql);
核心文件路径:/theme/html/demo*/src/js/components/core.datatable.js
所有参数的默认值见该文件 3369 行起。
data:
属性 | 功能 | 值 |
type | 数据源类型 | local / remote |
source | 数据源 | 链接或对象(见下方) |
pageSize | 每页项数 | 默认 10 |
saveState | 刷新、重新打开、返回时仍保持状态 | 默认 true |
serverPaging | 是否在服务端实现分页 | 默认 false |
serverFiltering | 是否在服务端实现筛选 | 默认 false |
serverSorting | 是否在服务端实现排序 | 默认 false |
autoColumns | 为远程数据源启用自动列功能 | 默认 false |
attr |
data.source:
属性 | 功能 | 值 |
url | 数据源地址 | |
params | 请求参数 |
|
headers |
自定义请求的头 |
|
map | 数据地图,作用是对返回的数据进行整理和定位 |
|
layout:
属性 | 功能 | 值 |
theme | 主题 | 默认 default |
class | 包裹的 CSS 样式 | |
scroll | 在需要时显示横向或纵向滚动条 | 默认 false |
height | 表格高度 | 默认 null |
minHeight | 表格最小高度 | 默认 null |
footer | 是否显示表格底部 | 默认 false |
header | 是否显示表头 | 默认 true |
customScrollbar | 自定义的滚动条 | 默认 true |
spinner |
Loading 样式 |
|
icons | 表格中的 icon |
|
sortable | 是否支持按列排序 | 默认 true |
resizable |
是否支持鼠标拖动改变列宽 | 默认 false |
filterable | 在列中过滤 | 默认 false |
pagination |
显示分页信息 | 默认 true |
editable |
行内编辑 | 默认 false |
columns |
列 | 见本文下方 |
search |
搜索 |
|
layout.columns:
属性 | 功能 | 解释 |
field | 字段名 | 对应 JSON 的属性名,点击表头时作为排序字段名 |
title | 表头名 | 显示在表格头部 |
sortable | 默认排序方式 | 可选:'asc' / 'desc' |
width | 单元格最小宽度 | 值与 CSS 值一致,填数字时默认单位 px |
type | 数据类型 | 'number' / 'date' 等,与本地排序有关 |
format | 数据格式化 | 例格式化日期:'YYYY-MM-DD' |
selector | 是否显示选择框 | 布尔值或对象,如:{ class: '' } |
textAlign | 文字对齐方式 | 'center' |
overflow | 内容超过单元格宽度时是否显示 | 'visible':永远显示 |
autoHide | 自适应显示/隐藏 | 布尔值 |
template | 用于显示内容的 HTML 模板 | function(row) { return row.Id; } |
sortCallback | 排序回调 | 自定义排序方式,参 local-sort.js |
其它:
属性 | 功能 | 解释 |
translate | 翻译 |
参 core.datatable.js 3512 行,简体中文示例:
|
extensions |
暂时没有找到对字符串内容进行自动 HTML 编码的属性,这可能带来 XSS 攻击风险,在 remote 方式中必须在服务端预先 HtmlEncode。即使在 layout.columns.template 中进行处理也是无济于事,恶意代码会在 ajax 加载完成后立即执行。
方法和事件:待完善。
更多信息请查询官方文档:https://keenthemes.com/keen/?page=docs§ion=html-components-datatable
form 提交 checkbox
<form method="get" asp-action="Index">
<input type="checkbox" name="a" value="1" /> 一
<input type="checkbox" name="a" value="2" /> 二
</form>
get 方式的 url 中会出现多个相同名称的参数
/abc?a=1&a=2
C# 使用数组类型接收参数
public IActionResult Index(string[] a)
{
}
Nuget 安装:X.PagedList.Mvc.Core
控制器:
using X.PagedList;
public IActionResult Index(int page = 1)
{
……
return View(q.ToPagedList(page, size));
}
视图:
@using X.PagedList
@using X.PagedList.Mvc.Core
@model IPagedList<xxx>
@Html.PagedListPager(Model, page => Url.Action("Index", new { page }))
自定义(options 默认值):
@Html.PagedListPager(
Model,
page => Url.Action("Index", new { page }),
new X.PagedList.Mvc.Common.PagedListRenderOptionsBase
{
HtmlEncoder = HtmlEncoder.get_Default(),
DisplayLinkToFirstPage = PagedListDisplayMode.IfNeeded,
DisplayLinkToLastPage = PagedListDisplayMode.IfNeeded,
DisplayLinkToPreviousPage = PagedListDisplayMode.IfNeeded,
DisplayLinkToNextPage = PagedListDisplayMode.IfNeeded,
DisplayLinkToIndividualPages = true,
DisplayPageCountAndCurrentLocation = false, // 显示总页数和当前页码
MaximumPageNumbersToDisplay = 10, // 最多显示页码数
DisplayEllipsesWhenNotShowingAllPageNumbers = true,
EllipsesFormat = "…",
LinkToFirstPageFormat = "<<",
LinkToPreviousPageFormat = "<",
LinkToIndividualPageFormat = "{0}",
LinkToNextPageFormat = ">",
LinkToLastPageFormat = ">>",
PageCountAndCurrentLocationFormat = "Page {0} of {1}.",
ItemSliceAndTotalFormat = "Showing items {0} through {1} of {2}.",
FunctionToDisplayEachPageNumber = null,
ClassToApplyToFirstListItemInPager = null,
ClassToApplyToLastListItemInPager = null,
ContainerDivClasses = new string[1]
{
"pagination-container"
},
UlElementClasses = new string[1]
{
"pagination"
},
LiElementClasses = Enumerable.Empty<string>(),
PageClasses = Enumerable.Empty<string>(),
UlElementattributes = null,
ActiveLiElementClass = "active",
EllipsesElementClass = "PagedList-ellipses",
PreviousElementClass = "PagedList-skipToPrevious",
NextElementClass = "PagedList-skipToNext",
})
保留地址栏参数:
@{
string query = Context.Request.QueryString.Value;
}
@Html.PagedListPager(Model, page => Regex.IsMatch(query, @"[?&]page=\d+")
? Regex.Replace(query, @"([?&])page=\d+", $"$1page={page}")
: (query.StartsWith("?") ? $"{query}&page={page}" : $"{query}?page={page}"),
new X.PagedList.Web.Common.PagedListRenderOptionsBase
{
DisplayPageCountAndCurrentLocation = true,
MaximumPageNumbersToDisplay = 5,
})
这里从查询字符串中判断并替换 page 值,如果有更简单的方法敬请告知。比如 Webdiyer 的分页组件会自动携带所有参数。
更多使用方式参官方文档:https://github.com/dncuug/X.PagedList
附应用于 Unify Template(一款基于 Bootstrap 的 HTML 模板)中的配置:
<style>
.u-pagination-v1-1--active .u-pagination-v1-1 { color: #fff; border-color: #72c02c; }
.PagedList-pageCountAndLocation { float: right !important; }
</style>
@{
string query = Context.Request.QueryString.Value;
}
@Html.PagedListPager(Model, page => Regex.IsMatch(query, @"[?&]page=\d+")
? Regex.Replace(query, @"([?&])page=\d+", $"$1page={page}")
: (query.StartsWith("?") ? $"{query}&page={page}" : $"{query}?page={page}"),
new X.PagedList.Web.Common.PagedListRenderOptionsBase
{
DisplayPageCountAndCurrentLocation = true,
MaximumPageNumbersToDisplay = 5,
UlElementClasses = new string[] { "list-inline" },
LiElementClasses = new string[] { "list-inline-item" },
PageClasses = new string[] { "u-pagination-v1__item", "u-pagination-v1-1", "g-pa-7-14" },
ActiveLiElementClass = "u-pagination-v1-1--active",
EllipsesElementClass = "g-pa-7-14",
})
安装 Nuget 包:
项目根目录创建绑定配置文件:bundleconfig.json
示例:
[
{
"outputFileName": "wwwroot/css/site.min.css",
"inputFiles": [
"wwwroot/css/site.css",
"wwwroot/css/custom.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]
配置文件中所有路径都相对于项目根目录(而非静态文件根目录 wwwroot),因此配置的路径都要以“wwwroot”开头。
outputFileName 是压缩合并后的文件,inputFiles 是被压缩合并的原始文件集合。
对于 js 配置部分,minify.enabled 配置是否缩小,renameLocals 配置是否修改变量名,sourceMap 配置是否生成 map 映射文件。
引用示例:
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
asp-append-version 表示是否在引用路径添加版本参数,可实现在文件有修改时及时在客户端浏览器中生效。
* 注意:有时候“生成”不一定生效,“重新生成”肯定会生效。
更多高级用法请参考官方文档。
小米电视 5 | 小米电视 5 Pro | |
亮点 | 全面屏、超薄金属机身、小爱同学、远场语音、PatchWall、4K、四单元喇叭、3GB+32GB | 量子点、MEMC运动补偿、4K+HDR10+、全面屏、小爱同学、远场语音、PatchWall、4GB+64GB |
分辨率 | 3840x2160(4K) | |
背光 | 侧入式 | |
可视角度 | 178° | |
刷新率 | 60Hz | |
广色域 | NTSC 85% | NTSC 108% |
CPU | Cortex A55 | |
GPU | Mali-G31 MP2 | |
内存 | 3GB | 4GB |
闪存 | 32GB | 64GB |
解码能力 | 8K 视频解码 | |
无线 | 2.4GHz/5GHz、蓝牙、红外 | |
尺寸 | 55 英寸 长 1227mm 高 717.8mm 含脚架高 773.2mm 65 英寸 长 1445.9mm 高 831.4mm 含脚架高 898mm 75 英寸 长 1676.5mm 宽 973.3mm 含脚架高 1037.7mm | |
首发价格 | 55" ¥2999 65" ¥3999 75" ¥7999 | 55" ¥3699 65" ¥4999 75" ¥9999 |
首发价格(舍入) | 55" ¥3000 65" ¥4000 75" ¥8000 | 55" ¥3700 65" ¥5000 75" ¥10000 |
* 本文凡使用变量 httpContextAccessor 或 _httpContextAccessor 的地方都需要需要注入 Microsoft.AspNetCore.Http.IHttpContextAccessor
* 在视图(view)中应以 ViewContext 类引出
获取当前页面网址:
using Microsoft.AspNetCore.Http.Extensions;
string url = Request.GetDisplayUrl();
获取当前访问的域名和端口:
获取来源:
Request.GetTypedHeaders().Referer
判断 Scheme 是否为 https:
Request.IsHttps
获取客户端IP和端口、服务器IP和端口:
获取浏览器用户代理(UserAgent):
httpContextAccessor.HttpContext.Request.Headers[HeaderNames.UserAgent];
获取当前请求的唯一标识:
httpContextAccessor.HttpContext.TraceIdentifier
返回结果:80000564-0002-f700-b63f-84710c7967bb
用途:可作为生成随机数的种子。
获取客户端IP地址:
httpContextAccessor.HttpContext.Connection.RemoteIpAddress
获取当前项目根目录磁盘路径:
AppContext.BaseDirectory // 以“\”结尾
注意:此路径到项目根目录,而非网站根目录,网站根目录请自行追加,默认为 wwwroot\
编码/解码:(参数和返回值均为 string?)
using System.Net;
WebUtility.HtmlEncode(myString)
WebUtility.HtmlDecode(myString)
WebUtility.UrlEncode(myString)
WebUtility.UrlDecode(myString)
更多:https://xoyozo.net/Blog/Index/Core
本文使用 Pomelo 提供的 Pomelo.EntityFrameworkCore.MySql,如使用 MySql.Data.EntityFrameworkCore 请移步。
对比 MySql.Data.EntityFrameworkCore 与 Pomelo.EntityFrameworkCore.MySql
本文以 Visual Studio 2019、ASP.NET Core 3.0 开发环境为例。
新建 ASP.NET Core Web 应用程序。
安装 NuGet 包:
Microsoft.EntityFrameworkCore.Tools
Pomelo.EntityFrameworkCore.MySql(3.0.0 预发行版以上)
根据已有数据库创建数据模型。在 NuGet 的程序包管理(Package Manager)控制台中(PowerShell)执行命令:
Scaffold-DbContext "server=数据库服务器;uid=数据库用户名;pwd=数据库密码;database=数据库名;" Pomelo.EntityFrameworkCore.MySql -OutputDir Data -Force
.Net Core CLi:
dotnet ef dbcontext scaffold "server=数据库服务器;uid=数据库用户名;pwd=数据库密码;database=数据库名;" Pomelo.EntityFrameworkCore.MySql -o Data -f
搞定。
补充:其它数据库提供程序请参考:https://docs.microsoft.com/zh-cn/ef/core/providers/
代码参数说明:
-OutputDir (-o) *** 实体文件所存放的文件目录
-ContextDir *** DbContext文件存放的目录
-Context *** DbContext文件名
-Schemas *** 需要生成实体数据的数据表所在的模式
-Tables(-t) *** 需要生成实体数据的数据表的集合
-DataAnnotations
-UseDatabaseNames 直接使用数据库中的表名和列名(某些版本不支持)
-Force (-f) 强制执行,重写已经存在的实体文件
更多高级用法请参考官方文档。