POST
$.post('AjaxCases', {
following: true,
success: null,
page: 1,
}, function (response) {
console.log(response.data)
}, 'json').fail(function (xhr, status, error) {
console.log(xhr.responseText);
});
[HttpPost]
public IActionResult AjaxCases([FromForm] AjaxCasesRequestModel req)
{
bool? following = req.following;
bool? success = req.success;
int page = req.page;
}
提示:只能传输对象(Object),不能传输数组(Array)。
首先我们要获取公众号的“__biz”值
在电脑浏览器上打开该公众号的任何一篇历史文章,在源代码中搜索“__biz=”就可以找到,该值以“==”结尾,例如找到:
__biz=MzIwNDcwODQ2NQ==
那么就可以拼成网址:(只能在微信中打开)
https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzIwNDcwODQ2NQ==#wechat_redirect
早期这个地址是这样的:
https://mp.weixin.qq.com/mp/getmasssendmsg?__biz=MzIwNDcwODQ2NQ==#wechat_redirect
现在它重定向到第一个地址去了,所以直接用新的就可以了。
若提示“页面无法打开”,可能的原因是没有加“#wechat_redirect”。
以 ASP.NET 6 返回 BadRequestObjectResult 为例。
public IActionResult Post()
{
return BadRequest("友好的错误提示语");
}
axios
请求示例:
axios.post(url, {})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error.response.data);
});
error 对象:
{
"message": "Request failed with status code 400",
"name": "AxiosError",
"code": "ERR_BAD_REQUEST",
"status": 400,
"response": {
"data": "友好的错误提示语",
"status": 400
}
}
jQuery
请求示例:
$.ajax 对象形式
$.ajax({
url: url,
type: 'POST',
data: {},
success: function (data) {
console.log(data)
},
error: function (jqXHR) {
console.log(jqXHR.responseText)
},
complete: function () {
},
});
$.ajax 事件处理形式(Event Handlers)jQuery 3+
$.ajax({
url: url,
type: 'POST',
data: {},
})
.done(function (data) {
console.log(data)
})
.fail(function (jqXHR) {
console.log(jqXHR.responseText)
})
.always(function () {
});
$.get / $.post / $.delete
$.post(url, {}, function (data) {
console.log(data)
})
.done(function (data) {
console.log(data)
})
.fail(function (jqXHR) {
console.log(jqXHR.responseText)
})
.always(function () {
});
jqXHR 对象:
{
"readyState": 4,
"responseText": "友好的错误提示语",
"status": 400,
"statusText": "error"
}
第一步,创建用户和 AccessKey
登录阿里云控制台后,从头像处进入 AccessKey 管理
“开始使用子用户 AccessKey”
点击“创建用户”,填写用户名(本文以 oss-bbs 为例),并勾选“Open API 调用访问”
点击确定创建成功,可以看到 AccessKey ID 和 AccessKey Secret
第二步,创建权限策略
在阿里云控制台左侧菜单“RAM 访问控制”中展开“权限管理”,选择“权限策略”
点击“创建权限策略”,切换到“脚本编辑”
输入以下策略文档(JSON 格式)
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"oss:ListBuckets",
"oss:GetBucketStat",
"oss:GetBucketInfo",
"oss:GetBucketAcl"
],
"Resource": "acs:oss:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"oss:Get*",
"oss:Put*",
"oss:List*",
"oss:DeleteObject"
],
"Resource": "acs:oss:*:*:bbs"
},
{
"Effect": "Allow",
"Action": [
"oss:Get*",
"oss:Put*",
"oss:List*",
"oss:DeleteObject"
],
"Resource": "acs:oss:*:*:bbs/*"
}
]
}
脚本中第一段表示允许用户登录,第二段表示允许操作 Bucket,第三段表示允许操作 Bucket 内的文件对象。
示例中的 bbs 是 Bucket Name,请修改为自己的 Bucket 名称。
点击下一步,填写名称,确定即可。
第三步,添加用户权限
转到“用户”页面,在刚才创建的用户行“添加权限”
切换到“自定义策略”,将上一步创建的权限策略添加到右侧“已选择”区
确定。
相关信息:
You have no right to access this object because of bucket acl.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace xxx.xxx.xxx.Controllers
{
public class DiscuzTinyintViewerController : Controller
{
public IActionResult Index()
{
using var context = new Data.xxx.xxxContext();
var conn = context.Database.GetDbConnection();
conn.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT `TABLE_NAME` FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE();";
Dictionary<string, List<FieldType>?> tables = new();
using var r = cmd.ExecuteReader();
while (r.Read())
{
tables.Add((string)r["TABLE_NAME"], null);
}
conn.Close();
foreach (var table in tables)
{
var conn2 = context.Database.GetDbConnection();
conn2.Open();
using var cmd2 = conn2.CreateCommand();
cmd2.CommandText = "DESCRIBE " + table.Key;
using var r2 = cmd2.ExecuteReader();
List<FieldType> fields = new();
while (r2.Read())
{
if (((string)r2[1]).Contains("tinyint(1)"))
{
fields.Add(new()
{
Field = (string)r2[0],
Type = (string)r2[1],
Null = (string)r2[2],
});
}
}
conn2.Close();
tables[table.Key] = fields;
}
foreach (var table in tables)
{
foreach (var f in table.Value)
{
var conn3 = context.Database.GetDbConnection();
conn3.Open();
using var cmd3 = conn3.CreateCommand();
cmd3.CommandText = $"SELECT {f.Field} as F, COUNT({f.Field}) as C FROM {table.Key} GROUP BY {f.Field}";
using var r3 = cmd3.ExecuteReader();
List<FieldType.ValueCount> vs = new();
while (r3.Read())
{
vs.Add(new() { Value = Convert.ToString(r3["F"]), Count = Convert.ToInt32(r3["C"]) });
}
conn3.Close();
f.groupedValuesCount = vs;
}
}
return Json(tables.Where(c => c.Value != null && c.Value.Count > 0));
}
private class FieldType
{
public string Field { get; set; }
public string Type { get; set; }
public string Null { get; set; }
public List<ValueCount> groupedValuesCount { get; set; }
public class ValueCount
{
public string Value { get; set; }
public int Count { get; set; }
}
public string RecommendedType
{
get
{
if (groupedValuesCount == null || groupedValuesCount.Count < 2)
{
return "无建议";
}
else if (groupedValuesCount.Count == 2 && groupedValuesCount.Any(c => c.Value == "0") && groupedValuesCount.Any(c => c.Value == "1"))
{
return "bool" + (Null == "YES" ? "?" : "");
}
else
{
return "sbyte" + (Null == "YES" ? "?" : "");
}
}
}
}
}
}
[{
"key": "pre_forum_post",
"value": [{
"field": "first",
"type": "tinyint(1)",
"null": "NO",
"groupedValuesCount": [{
"value": "0",
"count": 1395501
}, {
"value": "1",
"count": 179216
}],
"recommendedType": "bool"
}, {
"field": "invisible",
"type": "tinyint(1)",
"null": "NO",
"groupedValuesCount": [{
"value": "-5",
"count": 9457
}, {
"value": "-3",
"count": 1412
}, {
"value": "-2",
"count": 1122
}, {
"value": "-1",
"count": 402415
}, {
"value": "0",
"count": 1160308
}, {
"value": "1",
"count": 3
}],
"recommendedType": "sbyte"
}, {
"field": "anonymous",
"type": "tinyint(1)",
"null": "NO",
"groupedValuesCount": [{
"value": "0",
"count": 1574690
}, {
"value": "1",
"count": 27
}],
"recommendedType": "bool"
}, {
"field": "usesig",
"type": "tinyint(1)",
"null": "NO",
"groupedValuesCount": [{
"value": "0",
"count": 162487
}, {
"value": "1",
"count": 1412230
}],
"recommendedType": "bool"
}, {
"field": "htmlon",
"type": "tinyint(1)",
"null": "NO",
"groupedValuesCount": [{
"value": "0",
"count": 1574622
}, {
"value": "1",
"count": 95
}],
"recommendedType": "bool"
}, {
"field": "bbcodeoff",
"type": "tinyint(1)",
"null": "NO",
"groupedValuesCount": [{
"value": "-1",
"count": 935448
}, {
"value": "0",
"count": 639229
}, {
"value": "1",
"count": 40
}],
"recommendedType": "sbyte"
}, {
"field": "smileyoff",
"type": "tinyint(1)",
"null": "NO",
"groupedValuesCount": [{
"value": "-1",
"count": 1359482
}, {
"value": "0",
"count": 215186
}, {
"value": "1",
"count": 49
}],
"recommendedType": "sbyte"
}, {
"field": "parseurloff",
"type": "tinyint(1)",
"null": "NO",
"groupedValuesCount": [{
"value": "0",
"count": 1572844
}, {
"value": "1",
"count": 1873
}],
"recommendedType": "bool"
}, {
"field": "attachment",
"type": "tinyint(1)",
"null": "NO",
"groupedValuesCount": [{
"value": "0",
"count": 1535635
}, {
"value": "1",
"count": 2485
}, {
"value": "2",
"count": 36597
}],
"recommendedType": "sbyte"
}, {
"field": "comment",
"type": "tinyint(1)",
"null": "NO",
"groupedValuesCount": [{
"value": "0",
"count": 1569146
}, {
"value": "1",
"count": 5571
}],
"recommendedType": "bool"
}]
}]
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 后再回传给服务端。
Nuget 包:System.Runtime.Caching
依赖注入:
public class HomeController : Controller
{
private IMemoryCache _cache;
public HomeController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
定义键:
public static class CacheKeys
{
public static string Entry { get { return "_Entry"; } }
}
赋值与取值:
public IActionResult CacheTryGetValueSet()
{
DateTime cacheEntry;
// 尝试从缓存获取,若获取失败则重新赋值
if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
{
// 新的内容
cacheEntry = DateTime.Now;
// 缓存选项
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for this time, reset time if accessed.
.SetAbsoluteExpiration(TimeSpan.FromSeconds(3));
// 保存到缓存中
_cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
}
return View("Cache", cacheEntry);
}
注意:
.SetAbsoluteExpiration() 用于设置绝对过期时间,它表示只要时间一到就过期
.SetSlidingExpiration() 用于设置可调过期时间,它表示当离最后访问超过某个时间段后就过期
通过 nuget 安装 UEditorNetCore
从 UEditor 官网 下载最新的包 ueditorx_x_x_x-utf8-net.zip
解压包,并复制到项目的 wwwroot/lib 目录下,删除 net 目录。
根据 UEditorNetCore 官方的使用说明进行操作
步骤中,控制器 UEditorController 接替原 controller.ashx 的功能,用于统一处理上传逻辑
原 config.json 复制到项目根目录,用于配置上传相关设置(若更改文件名和路径在 services.AddUEditorService() 中处理)。
个人喜欢将 xxxPathFormat 值改为:upload/ueditor/{yyyy}{mm}{dd}/{time}{rand:6},方便日后迁移附件后进行批量替换。
记得配置 catcherLocalDomain 项。
上传相关的身份验证和授权控制请在 UEditorController 中处理,如:
public void Do()
{
if (用户未登录) { throw new System.Exception("请登录后再试"); }
_ue.DoAction(HttpContext);
}
如果图片仅仅上传到网站目录下,那么这样配置就结束了,如果要上传到阿里云 OSS 等第三方图床,那么继续往下看。
因 UEditorNetCore 虽然实现了上传到 FTP 和 OSS 的功能,但未提供配置相关的账号信息的途径,所以只能(1)重写 action,或(2)下载 github 的源代码,将核心项目加入到您的项目中进行修改。
重写 action 不方便后期维护,这里记录修改源码的方式。
将源码改造为可配置账号的上传到 OSS 的功能,具体步骤如下:
将 Consts.cs 从项目中排除。
打开 Handlers/UploadHandler.cs,找到 UploadConfig 类,将
FtpUpload 改为 OssUpload,
FtpAccount 改为 OssAccessKeyId,
FtpPwd 改为 OssAccessKeySecret,
FtpIp 改为 OssAccessEndpoint,
再添加一个属性 OssBucketName。
打开 Handlers/UploadHandler.cs,找到 Process() 方法,
将 UploadConfig.FtpUpload 改为 UploadConfig.OssUpload,
将 Consts.AliyunOssServer.* 改为 UploadConfig.Oss*。
打开 Handlers/CrawlerHandler.cs,找到 Fetch() 方法,
将 Config.GetValue<bool>("catcherFtpUpload") 改为 Config.GetValue<bool>("OssUpload"),
将 Consts.AliyunOssServer.* 改为 Config.GetString("Oss*")。
打开 UEditorActionCollection.cs,找到 UploadImageAction,将
FtpUpload = Config.GetValue<bool>("imageFtpUpload"), FtpAccount = Consts.ImgFtpServer.account, FtpPwd = Consts.ImgFtpServer.pwd, FtpIp = Consts.ImgFtpServer.ip,
替换为
OssUpload = Config.GetValue<bool>("OssUpload"), OssAccessKeyId = Config.GetString("OssAccessKeyId"), OssAccessKeySecret = Config.GetString("OssAccessKeySecret"), OssAccessEndpoint = Config.GetString("OssAccessEndpoint"), OssBucketName = Config.GetString("OssBucketName"),
其余 3 个 Action(UploadScrawlAction、UploadVideoAction、UploadFileAction)按同样的方式修改。
在所有创建 UploadHandler 对象时补充添加 SaveAbsolutePath 属性。
打开 config.json,添加相关配置项(注:配置文件中的 *FtpUpload 全部废弃,统一由 OssUpload 决定)
"OssUpload": true, "OssAccessEndpoint": "", "OssAccessKeyId": "", "OssAccessKeySecret": "", "OssBucketName": "",
将 xxxUrlPrefix 的值改为 OSS 对应的 CDN 网址(以 / 结尾,如://cdn.xoyozo.net/)。
其它注意点:
若使用 UEditorNetCore github 提供的源代码类库代替 nuget,且使用本地存储,那么需要将 Handlers 目录中与定义 savePath 相关的代码(查找 var savePath)替换成被注释的行
本文适用于 Window,CentOS(Linux) 系统请移步:http://xoyozo.net/Blog/Details/inotify-tools
FileSystemWatcher
关于监视文件系统事件的介绍
https://docs.microsoft.com/zh-cn/previous-versions/visualstudio/visual-studio-2008/ch2s8yd7(v=vs.90)
以控制台应用程序为例:
using System;
using System.IO;
using System.Security.Permissions;
public class Watcher
{
public static void Main()
{
Run();
}
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
private static void Run()
{
string[] args = Environment.GetCommandLineArgs();
// If a directory is not specified, exit program.
if (args.Length != 2)
{
// Display the proper way to call the program.
Console.WriteLine("Usage: Watcher.exe (directory)");
return;
}
// Create a new FileSystemWatcher and set its properties.
using (FileSystemWatcher watcher = new FileSystemWatcher())
{
watcher.Path = args[1];
// Watch for changes in LastAccess and LastWrite times, and
// the renaming of files or directories.
watcher.NotifyFilter = NotifyFilters.LastAccess
| NotifyFilters.LastWrite
| NotifyFilters.FileName
| NotifyFilters.DirectoryName;
// Only watch text files.
watcher.Filter = "*.txt";
// Add event handlers.
watcher.Changed += OnChanged;
watcher.Created += OnChanged;
watcher.Deleted += OnChanged;
watcher.Renamed += OnRenamed;
// Begin watching.
watcher.EnableRaisingEvents = true;
// Wait for the user to quit the program.
Console.WriteLine("Press 'q' to quit the sample.");
while (Console.Read() != 'q') ;
}
}
// Define the event handlers.
private static void OnChanged(object source, FileSystemEventArgs e) =>
// Specify what is done when a file is changed, created, or deleted.
Console.WriteLine($"File: {e.FullPath} {e.ChangeType}");
private static void OnRenamed(object source, RenamedEventArgs e) =>
// Specify what is done when a file is renamed.
Console.WriteLine($"File: {e.OldFullPath} renamed to {e.FullPath}");
}
前端引用 jquery、jquery-ui(或 jquery.ui.widget),以及 jquery.fileupload.js
$('#xxx').fileupload({
url: 'FileUpload',
add: function (e, data) {
console.log('add', data);
data.submit();
},
success: function (response, status) {
console.log('success', response);
toastr.success('上传成功!文件名:' + response);
},
error: function (error) {
console.log('error', error);
toastr.error("上传失败:" + error.responseText);
}
});
服务端接收文件:
[HttpPost]
public ActionResult FileUpload([FromForm(Name = "files[]")] IFormFile file)
{
try
{
return Ok(file.FileName);
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
}
此处 Name = "files[]" 值是由 <input type="file" name="这里定的" />
如果 input 没有指定 name,那么一般为 file[],或者在 Chrome 的开发者工具中,切换到 Network 标签页,选中 post 的目标请求,在 Headers 的 Form Data 中可以看到 name="xxx"。