nginx 配置文件中不能随意嵌套 if,而且 if 中也没有 and 和 or 逻辑,不过可以借用变量来实现:
set $a "";
if ($request_uri ~* "action=postreview")
{
set $a "${a}1";
}
if ($http_referer !~* "^https?://([^/]+\.)?domains\.com([:/?#]|$)")
{
set $a "${a}1";
}
if ($a = '11')
{
return 403;
}
fastcgi...上例中实现了 url 中包含 action=postreview 且来源不是本站的情况拒绝访问。
一般放在 fastcgi 所在 location,譬如宝塔面板的 enable-php-xx.conf 文件中。
.NET 项目发布到 Linux / CentOS / nginx 上,调用 RedirectToAction 方法跳转到 127.0.0.1:443,而不是正在访问的域名,怎么办?
打开该网站的 nginx 配置文件,找到反射代理配置(proxy_pass),将:
proxy_set_header Host 127.0.0.1:$server_port;改为
proxy_set_header Host $host;-
在 access_by_lua_block 代码块中实现远程鉴权:
#鉴权-START #resolver 223.5.5.5; # cat /etc/resolv.conf access_by_lua_block { local http = require("resty.http") local httpc = http.new() httpc:set_timeout(500) -- 连接超时 local res, err = httpc:request_uri("https://鉴权地址/", { method = "GET", headers = { ["X-Real-IP"] = ngx.var.remote_addr, ["User-Agent"] = ngx.var.http_user_agent, ["X-Forwarded-Host"] = ngx.var.host, ["X-Forwarded-Uri"] = ngx.var.request_uri, }, ssl_verify = false, -- 禁用 SSL 验证 timeout = 500, -- 读取超时 }) if not res then ngx.log(ngx.ERR, "Failed to request: " .. err) end if res and res.status == 403 then ngx.exit(ngx.HTTP_FORBIDDEN) -- return ngx.redirect("https://一个显示403友好信息的页面.html") end } #鉴权-END注意更改接口地址和友情显示 403 页面地址。
本示例仅捕获 403 状态码,500、408 等其它异常情况视为允许访问,请根据业务需求自行添加状态码的判断。
若超时也会进入
if not res then代码块。建议将此代码部署在 nginx 主配置文件的 http 代码块中(宝塔面板中的路径:/www/server/nginx/nginx/conf/nginx.conf),如果你只想为单个网站鉴权,也可以放在网站配置文件的 server 块中。
-
若鉴权接口在私网中,建议将鉴权接口域名和私网 IP 添加到 hosts 文件中。
附
-
直接输出字符串
ngx.header.content_type = "text/plain"; ngx.say("hello world!") -
输出到日志
ngx.log(ngx.ERR, "Response status: " .. res.status)日志在网站的 站名.error.log 中查看。
宝塔面板查看方式:日志 - 网站日志 - 异常
-
若想获取服务器 CPU 使用率等信息并传递给远程鉴权接口,请参考此文。
-
常见问题
no resolver defined to resolve
原因:没有定义 DNS 解析器
解决方法:在 http 块或 server 块中添加
resolver 8.8.8.8 valid=30s;,推荐使用接入商自己的公共 DNS,如果走内网,推荐使用本机 DNS。unable to get local issuer certificate
原因:没有配置 SSL 证书信息
解决方法:添加 request_uri 参数:
ssl_verify = true, -- 启用 SSL 验证 ssl_trusted_certificate = "证书路径", -- 指定 CA 证书路径或
ssl_verify = false, -- 禁用 SSL 验证 -
若您不想用 lua,可以用 nginx 原生自带的 auth_request 模块来实现。
-
若 WAF 返回拒绝访问时需要 302 跳转(即 return ngx.redirect(...)),那么最好加上禁止缓存的标识,防止 CDN 污染:
if res and res.status == 403 then -- ===== 禁止 CDN 缓存此响应 ===== ngx.header["Cache-Control"] = "no-store, no-cache, must-revalidate, proxy-revalidate" ngx.header["Pragma"] = "no-cache" ngx.header["Expires"] = "0" -- 可选:添加标记头,方便调试 ngx.header["X-WAF-Action"] = "blocked" return ngx.redirect("https://...") end
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 后再回传给服务端,vue 绑定方式参此文。
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() 用于设置可调过期时间,它表示当离最后访问超过某个时间段后就过期