SignalR微软官方文档
TMS
关键字:大车 GPS 交通局 短信 休息15分钟/4小时
火星坐标=>大地坐标 (如果要用百度地图 还要将大地坐标=>百度坐标)
电子围栏
①模拟制造数据(预制路线): C/S架构 1条/5秒 非关系型数据库/时序数据库 17280条/日/车
SignalR:长连接 API主动同步数据到客户端
数据库索引
联合索引
覆盖索引
定时调用数据库函数清理索引碎片
show table status like 'Dic';
CREATE INDEX INDEX_Identification ON Dic(Identification)
-- 查询数据表的索引
SHOW indexes FROM Dic
SHOW KEYS FROM Dic
-- 针对MyISAM引擎表使用
OPTIMIZE TABLE Dic
-- 针对InnoDB引擎表使用
ALTER TABLE Dic ENGINE = innodb
-- 执行计划查看是否命中索引
EXPLAIN SELECT Id FROM Dic;
EXPLAIN SELECT Identification FROM Dic
-- SIMPLE:查询中不包含子查询或者UNION
-- PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为PRIMARY
-- SUBQUERY:在SELECT或WHERE列表中包含了子查询,该子查询被标记为SUBQUERY
-- DERIVED:在FROM列表中包含的子查询被标记为DERIVED(衍生)
-- Type
-- 表示MySQL在表中找到所需行的方式,又称“访问类型”,常见有以下几种:
--
-- ALL:Full Table Scan, MySQL将进行全表扫描;
-- Index:Full Index Scan,index与ALL区别为index类型只遍历索引树;
-- range:range Index Scan,对索引的扫描开始于某一点,返回匹配值域的行,常见于between、<、>等的查询;
-- ref: 表示非唯一性索引
-- eq_ref: 表示唯一索引
-- const: 一次就找到数据的唯一索引
-- all < index < range < ref < eq_ref < const < system
--
-- 可以使用到索引,除了 index_merge 之外,其他的 type 只可以用到一个索引。一般来说,得保证查询至少达到 range 级别,最好能达到 ref
--
-- possible_keys : 可能用到的索引
-- key : 实际使用到的索引
-- key_len: 使用到的索引长度,根据索引组合字段类型所占的字节数来计算。MySQL在执行计划中输出key_len列主要是为了让我们区分某个使用联合索引的查询具体用了几个索引列。
-- ref 显示哪一列索引有被用到,
-- row 大致估算找到目标记录所需要读取的行数; 这个越低越好;
-- fitered 显示通过条件过滤出的行数的百分比估计值;
-- extra mysql解析查询过程中的额外信息
-- distinct 找到第一个匹配的元组后立即停止找相同值得操作
-- using filesort 可能出现了排序操作和使用索引导致结果排序
-- using temporary 用到了临时表保存中间结果,常用与groupby 操作,一般看到他说明需要优化了
-- using index 使用到了索引覆盖
-- using index condition 发生索引下推
-- using where 会根据查询条件过滤出结果集
-- using join buffer
-- looseScan
-- FirstMatch
-- SHOW TABLES FROM ProductionDB WHERE tables_in_productiondb LIKE 'Dic%';
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'ProductionDB' AND TABLE_NAME LIKE 'Dic%';
-- DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- 这里的处理程序做了以下几件事:
-- 1、DECLARE CONTINUE HANDLER: 声明一个处理程序,该处理程序在发生指定条件时继续执行后续的代码(而不是立即终止存储过程或函数)。
-- 2、FOR NOT FOUND: 指定处理程序应用于“NOT FOUND”条件。
-- 3、SET done = TRUE;: 当“NOT FOUND”条件被触发时,将变量done设置为TRUE。
-- 在游标循环中,done变量通常用于检查是否还有更多的行可以从游标中获取。一旦done被设置为TRUE,循环就会终止。
-- 当你使用动态SQL(即SQL语句在运行时构建)时,你可能会使用 PREPARE 语句来预编译一个SQL语句,并给它一个名字(在这个例子中是 stmt)。这样做有几个好处,包括允许你多次执行相同的SQL语句而无需重复解析它,以及允许你在SQL语句中包含变量。
-- 但是,当你不再需要这个预编译的SQL语句时,你应该使用 DEALLOCATE PREPARE 语句来释放与其关联的资源。如果你不这样做,这些资源可能会保持被占用状态,这可能会导致性能问题或资源耗尽。
DELIMITER
//
CREATE PROCEDURE QueryDicTables()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE tableName CHAR(255);
DECLARE cur CURSOR FOR
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'ProductionDB'
AND TABLE_NAME LIKE 'Dic%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP -- LEAVE read_loop;
FETCH cur INTO tableName;
IF done THEN
LEAVE read_loop;
END IF;
-- 这里使用PREPARE和EXECUTE来执行动态SQL
SET @sql = CONCAT('SELECT * FROM ', tableName, ';');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur;
END
//
DELIMITER ;
CALL QueryDicTables();
ABP MQ
发布
//不再阐述配置
private readonly IDistributedEventBus _distributedEventBus;
//ctor注入
public async Task<T> MethodName()
{
await _distributedEventBus.PublishAsync(new ETO)
}
//ETO定义时也可以给其加一个事件名称,以属性方式放在表上[EventName("MyApp.Product.StockChange")]
订阅
//继承事件为ETO的接口 实现Task HandleEventAsync(TEvent eventData)的方法 此处TEvent为上述ETO
:IDistributedEventHandler<TEvent>()
public Task HandleEventAsync(ETO eventData)
{
//逻辑
return Task.Completed;
}
ABP 实体&聚合根
//官方案例
public class Order : AggregateRoot<Guid>
{
public virtual string ReferenceNo { get; protected set; }
public virtual int TotalItemCount { get; protected set; }
public virtual DateTime CreationTime { get; protected set; }
public virtual List<OrderLine> OrderLines { get; protected set; }
protected Order()
{
}
public Order(Guid id, string referenceNo)
{
Check.NotNull(referenceNo, nameof(referenceNo));
Id = id;
ReferenceNo = referenceNo;
OrderLines = new List<OrderLine>();
}
public void AddProduct(Guid productId, int count)
{
if (count <= 0)
{
throw new ArgumentException(
"You can not add zero or negative count of products!",
nameof(count)
);
}
var existingLine = OrderLines.FirstOrDefault(ol => ol.ProductId == productId);
if (existingLine == null)
{
OrderLines.Add(new OrderLine(this.Id, productId, count));
}
else
{
existingLine.ChangeCount(existingLine.Count + count);
}
TotalItemCount += count;
}
}
//实体
public class OrderLine : Entity
{
public virtual Guid OrderId { get; protected set; }
//OrderId和ProductId是组合主键
public virtual Guid ProductId { get; protected set; }
public virtual int Count { get; protected set; }
protected OrderLine()
{
}
internal OrderLine(Guid orderId, Guid productId, int count)
{
OrderId = orderId;
ProductId = productId;
Count = count;
}
internal void ChangeCount(int newCount)
{
Count = newCount;
}
public override object[] GetKeys()
{
return new Object[] {OrderId, ProductId};
}
}
//值对象
public class Address : ValueObject
{
public Guid CityId { get; private set; }
public string Street { get; private set; }
public int Number { get; private set; }
private Address()
{
}
public Address(
Guid cityId,
string street,
int number)
{
CityId = cityId;
Street = street;
Number = number;
}
//值对象类必须实现 GetAtomicValues()方法来返回原始值
protected override IEnumerable<object> GetAtomicValues()
{
yield return Street;
yield return CityId;
yield return Number;
}
}
ABP 工作单元
Redis集群 哨兵 主从复制
"配主不配从"
SLAVEOF主服务器IP:Port
分布式锁
什么是锁
- 在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。
- 而同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记。这个标记可以理解为锁。
分布式场景
- 分布式与单机情况下最大的不同在于其不是多线程而是多进程。
- 多线程由于可以共享堆内存,因此可以简单的采取内存作为标记存储位置。而进程之间甚至可能都不在同一台物理机上,因此需要将标记存储在一个所有进程都能看到的地方。
基于 redis 的 SETNX()、GET()、GETSET()方法做分布式锁
这个方案的背景主要是在SETNX() 和 EXPIRE() 的方案上针对可能存在的死锁问题,做了一些优化。
GETSET()这个命令主要有两个参数 GETSET(key,newValue)。该方法是原子的,对 key 设置 newValue 这个值,并且返回 key 原来的旧值。假设 key 原来是不存在的,那么多次执行这个命令,会出现下边的效果:
GETSET(key, "value1") 返回 null 此时 key 的值会被设置为 value1
GETSET(key, "value2") 返回 value1 此时 key 的值会被设置为 value2
依次类推!
使用步骤
- SETNX(lockkey, 当前时间+过期超时时间),如果返回 1,则获取锁成功;如果返回 0 则没有获取到锁,转向 2。
- GET(lockkey) 获取值 oldExpireTime ,并将这个 value 值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向 3。
- 计算 newExpireTime = 当前时间+过期超时时间,然后 GETSET(lockkey, newExpireTime) 会返回当前 lockkey 的值currentExpireTime。
- 判断 currentExpireTime 与 oldExpireTime 是否相等,如果相等,说明当前 GETSET设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。
- 在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行 delete 释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。
代码
import cn.com.tpig.cache.redis.RedisService;
import cn.com.tpig.utils.SpringUtils;
//redis分布式锁
public final class RedisLockUtil {
private static final int defaultExpire = 60;
private RedisLockUtil() {
//
}
/**
* 加锁
* @param key redis key
* @param expire 过期时间,单位秒
* @return true:加锁成功,false,加锁失败
*/
public static boolean lock(String key, int expire) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long status = redisService.setnx(key, "1");
if(status == 1) {
redisService.expire(key, expire);
return true;
}
return false;
}
public static boolean lock(String key) {
return lock2(key, defaultExpire);
}
/**
* 加锁
* @param key redis key
* @param expire 过期时间,单位秒
* @return true:加锁成功,false,加锁失败
*/
public static boolean lock2(String key, int expire) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long value = System.currentTimeMillis() + expire;
long status = redisService.setnx(key, String.valueOf(value));
if(status == 1) {
return true;
}
long oldExpireTime = Long.parseLong(redisService.get(key, "0"));
if(oldExpireTime < System.currentTimeMillis()) {
//超时
long newExpireTime = System.currentTimeMillis() + expire;
long currentExpireTime = Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime)));
if(currentExpireTime == oldExpireTime) {
return true;
}
}
return false;
}
public static void unLock1(String key) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
redisService.del(key);
}
public static void unLock2(String key) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long oldExpireTime = Long.parseLong(redisService.get(key, "0"));
if(oldExpireTime > System.currentTimeMillis()) {
redisService.del(key);
}
}
}
public void drawRedPacket(long userId) {
String key = "draw.redpacket.userid:" + userId;
boolean lock = RedisLockUtil.lock2(key, 60);
if(lock) {
try {
//领取操作
} finally {
//释放锁
RedisLockUtil.unLock(key);
}
} else {
new RuntimeException("重复领取奖励");
}
}
MySQL存储过程
-- 考虑使用数据库计划按时间自动创建新表 事实上MySQL一般通过分区来实现条件分表 $$
DELIMITER //
CREATE PROCEDURE auto_horizontal_sharding()
BEGIN
-- 声明变量
DECLARE total_rows INT;
DECLARE table_name VARCHAR(255);
-- 获取当前数据量
SELECT COUNT(*) INTO total_rows FROM LambInfos;
-- 判断数据量是否达到200万
IF total_rows >= 2000000 THEN
-- 构建新表名
SET table_name = CONCAT('LambInfos_', FLOOR(total_rows / 2000000));
-- 检查新表是否存在,不存在则创建
IF NOT EXISTS (SELECT table_name FROM information_schema.tables WHERE table_name = table_name) THEN
-- 使用LIKE LambInfos指定新表的结构与LambInfos表相同
SET @sql = CONCAT('CREATE TABLE ', table_name, ' LIKE LambInfos');
PREPARE stmt FROM @sql;
EXECUTE stmt;
-- DEALLOCATE 语句用于释放之前使用 PREPARE 准备的语句对象
DEALLOCATE PREPARE stmt;
END IF;
-- 将数据插入新表
SET @sql = CONCAT('INSERT INTO ', table_name, ' SELECT * FROM LambInfos');
PREPARE stmt FROM @sql;
EXECUTE stmt;
-- 释放
DEALLOCATE PREPARE stmt;
END IF;
END//
DELIMITER ;
CALL auto_horizontal_sharding();
MongoDB
MySQL主从复制
Nginx配置负载均衡
关键字:upstream
B树和B+树的区别
实时存栏
Linux使用Nginx部署前端静态页面(以Ubuntu20.04为例)
1、确保Linux环境下已经安装Nginx
2、启动时候若显示端口80被占用: Starting nginx: [emerg]: bind() to 0.0.0.0:80 failed (98: Address already in use),修改文件:/etc/nginx/sites-available/default,去掉 listen 前面的 # 号 , # 号在该文件里是注释的意思 , 并且把 listen 后面的 80 端口号改为自己的端口,访问是需要添加端口号
vi /etc/nginx/sites-available/default
server
{
//root为前端项目的根目录,所有的页面都要以该根目录的相对路径去访问
//通过宝塔面板安装的nginx默认静态文件存放路径为/var/www/html
root /var/www/html;
//设置主页,例:/views/index.html;根目录的相对路径设置
index index.html;
}
3、修改配置文件后要重新启动Nginx
service nginx reload
4、查看Nginx状态
systemctl status nginx
ELK
Kibana是一个开源的分析与可视化平台,设计出来用于和Elasticsearch一起使用的。你可以用kibana搜索、查看存放在Elasticsearch中的数据。Kibana与Elasticsearch的交互方式是各种不同的图表、表格、地图等,直观的展示数据,从而达到高级的数据分析与可视化的目的。
Elasticsearch、Logstash和Kibana这三个技术就是我们常说的ELK技术栈,可以说这三个技术的组合是大数据领域中一个很巧妙的设计。一种很典型的MVC思想,模型持久层,视图层和控制层。Logstash担任控制层的角色,负责搜集和过滤数据。Elasticsearch担任数据持久层的角色,负责储存数据。Kibana担任视图层角色,拥有各种维度的查询和分析,并使用图形化的界面展示存放在Elasticsearch中的数据。
难点
悲观锁、乐观锁
单例懒汉、饿汉
Redis分布式锁的实现
给锁加过期时间,到期自动释放,防止死锁
Ids4单体
Ids4微服务
CI/CD配置中的问题
1、已有代理情况下要先移除上一个代理
2、镜像容器的访问凭证,保证服务器可以连接镜像仓库
3、打开触发器,使CI执行后可以自动触发CD
RabbitMQ ACK
关键词:autoAck basicAck basicReject basicNack
basicQos
void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException;
// prefetchSize = 0
void basicQos(int prefetchCount, boolean global) throws IOException;
// prefetchSize = 0 , global = false
void basicQos(int prefetchCount) throws IOException;
prefetchSize:预读取的消息内容大小上限(包含),可以简单理解为消息有效载荷字节数组的最大长度限制,0表示无上限。
prefetchCount:预读取的消息数量上限,0表示无上限。
global:false表示prefetchCount单独应用于信道上的每个新消费者,true表示prefetchCount在同一个信道上的消费者共享。
Polly
重试策略(Retry):当服务调用失败时,Polly可以自动进行重试,这有助于处理那些可能因为暂时性问题导致的服务不可用情况。
断路器(Circuit Breaker):当检测到服务连续不可用时,断路器策略会介入,快速返回错误响应,避免对下游服务的持续请求,从而预防服务雪崩现象。
超时策略(Timeout):为服务调用设置一个最大执行时间,超过这个时间的服务调用将被认为失败,可以采取预设的应对措施。
舱壁隔离(Bulkhead Isolation):通过限制对服务的并发调用数量,防止因某个服务的问题影响到整个系统的稳定性。
缓存策略(Cache):提供一种机制,可以在服务调用的结果不变的情况下直接使用缓存结果,减少不必要的服务调用。
降级策略(Fallback):当服务调用失败时,可以提供一个备用的逻辑或者数据作为响应,提高用户体验。
策略组合(PolicyWrap) : Polly针对不同的故障有不同的策略,我们可以灵活的组合策略,上述的六种策略可以灵活组合使用。
UOW
一个事务也会包装多个数据库操作,并在最后提交更改。不过工作单元与事务具有更多的不同,事务的关键特征是支持ACID原则,工作单元并不需要实现得这么复杂,工作单元只是将所有修改状态保存下来,在提交时委托给事务完成。所以工作单元本身不具有隔离性,这意味着工作单元只能在单线程中工作,如果同时让多个线程访问工作单元,就会导致数据错乱。
工作单元对并发的协调,是依靠聚合根上的乐观离线锁,以及数据库事务的并发控制能力来共同完成的。
基于Azure DevOps 的 CICD 项目部署(.Net Core)
-
创建新项目
-
创建项目名称
-
选择仓库地址
-
选择空模板
-
创建代理池
-
按照以下步骤把代理部署到服务器上
![
-
连接你的服务器
-
创建新的文件夹
mkdir myange cd myagent
-
可通过链接下载文件
wget https://vstsagentpackage.azureedge.net/agent/3.238.0/vsts-agent-win-x64-3.238.0.zip
-
创建新的文件夹进入并解压缩
mkdir myagent && cd myagent tar zxvf ~/Downloads/vsts-agent-linux-x64-3.238.0.tar.gz
-
解压完成执行 ./config.sh
-
这里可能会出现一些错误提示:Must not run with sudo 运行下面代码
export AGENT_ALLOW_RUNASROOT="1" ./config.sh
-
先创建 Token 点击右上角小人
-
现在根据以下步骤执行,运行代理
![](/i/l/?n=24&i=blog/3394716/202405/3394716-20240518110108084-1111103607.png)
这里显示绿色为开启状态
![](/i/l/?n=24&i=blog/3394716/202405/3394716-20240518110126573-1426654634.png)
-
部署CI部分
-
现在要创建阿里云镜像服务
-
配置Docker 拉取和推送 根据以下两个步骤进行拉取和推送的配置
-
配置完成后点上方的保存,运行CI部分,时间会很长,耐心等待,如出现问题可以百度等。
-
现在配置CD部分
- 创建空模版
- 选中你的CI部分
- 补充要执行的任务
- 创建一个SSH用于连接服务器
-
其中 registry.cn-hangzhou.aliyuncs.com/TextInfor/wudia 是需要更改为阿里云的 公网地址
-
给镜像起名,在判断是否有用这个容器,有就停了在删除,再看镜像有没有,有就删了,创建一个文件夹写日志,主要的就是在服务器拉文件,在运行文件
#!/bin/bash name="webapp01" cid=`docker inspect --format '{{.Id}}' ${name} 2>/dev/null` if [[ ${#cid} -gt 0 ]]; then docker stop $name docker rm $name echo "successed deleted container ${name}" fi imageid=`docker images --format {{.ID}} $name` echo "准备删除旧镜像${imageid}" if [[ ${#imageid} -gt 0 ]]; then docker rmi -f $imageid echo "successed deleted old image ${name}" fi logfile="/var/log/servicelog/$name" if [[ ! -x "$logfile" ]]; then mkdir -p "$logfile" echo "已成功创建日志文件夹" else echo "文件存夹已存在" fi docker pull registry.cn-hangzhou.aliyuncs.com/TextInfor/wudia docker run -it -d -p 8002:80 --name webapp01 registry.cn-hangzhou.aliyuncs.com/TextInfor/wudia --privileged=true docker logs webapp01
-
-
配置完成之后返回这级
-
开启触发器CI触发CD
-
开启运行即可
阿里云手机短信验证码服务
-
阿里云创建账号 https://home.console.aliyun.com/home/dashboard/ProductAndService
-
搜索短信服务
-
选择签名、资质管理、新增资质
-
创建签名
- 创建新模版,其中对映签名等信息
-
查看模版
-
获取ID 和 Key
(1).选中头像,创建AccessKey
需要安装包
-
创建工具类
/// <summary> /// 发送手机短信信息(通过阿里云SDK) /// </summary> /// <param name="aliAccessKey">授权id(阿里云账号的AccessKeyId)</param> /// <param name="aliAccessSecret">授权密钥(阿里云账号的AccessKeySecret)</param> /// <param name="phoneNumber">接收手机号(多个用逗号隔开)</param> /// <param name="signName">短信签名</param> /// <param name="tempCode">短信模板ID</param> /// <param name="tempParam">短信模板变量</param> /// <returns></returns> public static JObject SendMessageByAliSms(string aliAccessKey, string aliAccessSecret, string phoneNumber, string signName, string tempCode, string tempParam) { //返回结果对象 JObject result = new JObject(); //接口文档(里面有sdk):https://help.aliyun.com/product/44282.html?spm=5176.12226155.0.0.33ac1cbeMfrIwkhttps://home.firefoxchina.cn IClientProfile profile = DefaultProfile.GetProfile("cn-hangzhou", aliAccessKey, aliAccessSecret); //cn-hangzhou: 默认节点 DefaultAcsClient client = new DefaultAcsClient(profile); CommonRequest request = new CommonRequest(); request.Method = MethodType.POST; //请求方式 request.Domain = "dysmsapi.aliyuncs.com"; //请求域名 request.Version = "2017-05-25"; //请求版本 request.Action = "SendSms"; //请求操作 request.AddQueryParameters("PhoneNumbers", phoneNumber); request.AddQueryParameters("SignName", signName); request.AddQueryParameters("TemplateCode", tempCode); request.AddQueryParameters("TemplateParam", tempParam); try { //发送请求请返回响应 CommonResponse response = client.GetCommonResponse(request); //获取返回的内容 string content = Encoding.Default.GetString(response.HttpResponse.Content); //序列化成对象(Code: 请求状态码,Message:状态码描述,BizId:发送回执ID, RequestId: 请求ID) result = (JObject)JsonConvert.DeserializeObject(content); } catch (ServerException e) { result.Add("Code", "500"); result.Add("Message", "短信服务异常:" + e.Message); } catch (ClientException e) { result.Add("Code", "501"); result.Add("Message", "客户端异常:" + e.Message); } return result; } #endregion } public static class AliInfor { #region 阿里云平台短信服务 //获取配置文件的授权ID、授权密钥 public static string aliAccessKeyId = "";//输入ID public static string aliAccessKeySecret = "";//输入秘钥 /// <summary> /// 发送手机短信验证码(阿里云平台) /// </summary> /// <param name="mobile">接收手机</param> /// <param name="signName">短信签名</param> /// <param name="tempCode">短信模板ID</param> /// <param name="mobileCode">短信验证码</param> /// <returns></returns> public static bool SendMobileMessageByAli(string mobile, string signName, string tempCode, string mobileCode) { //拼接读短信目标变量值 "{\"code\":\"1234\"}" string tempParam = "{'code" + "':" + mobileCode + "}";// $"code:{mobileCode}"; //发送短信并返回结果 JObject result = SendMessageByAliSms(aliAccessKeyId, aliAccessKeySecret, mobile, signName, tempCode, tempParam); //返回代码 if (result["Code"].ToString().ToLower() == "ok") { return true; } else { return false; } }
-
API调用
/// <summary> /// 发送验证码 /// </summary> /// <param name="uTel">手机号码</param> /// <param name="ges">验证码</param> public void GetTelLogin(string? uTel,string? ges) { bool isSuccess = AliInfor.SendMobileMessageByAli(uTel, "系统名称", "SMS_465635094", ges); }
基于.net core 七牛云上传图片上传
- 创建七牛云账号,链接:https://portal.qiniu.com/home
- 新建空间(可以选择华北,可设置公开或私有(公开返回前台不需要token))
- 单图片上传
下载包
```c#
/// <summary>
/// 上传文件
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
[HttpPost] // 指定该方法处理 HTTP POST 请求
public async Task<IActionResult> UploadImages(IFormFile file) // 定义一个异步方法,接收一个文件类型的参数
{
if (file == null || file.Length == 0) // 检查文件是否为空或文件大小
{
return BadRequest("文件不能为空");
}
string saveKey = "Pic/" + file.FileName; // 设置上传文件保存的路径和文件名,可以随便输入 不用创建可自动生成
string bucket = "空间管理的名称"; // 七牛云存储空间名称 七牛云仓库名称
Mac mac = new Mac("AK", "SK"); // 替换为您七牛云账号的访问密钥和密钥
// 设置上传策略
PutPolicy putPolicy = new PutPolicy();
putPolicy.Scope = bucket + ":" + saveKey; // 设置 Scope 为具体的文件路径,即指定存储空间和保存路径
// 创建上传凭证
string token = Auth.CreateUploadToken(mac, putPolicy.ToJsonString()); // 生成上传凭证,凭证包含了上传策略信息
// 配置上传管理器
Config config = new Config();
config.Zone = Zone.ZONE_CN_North; // 设置上传区域,这里是华北区域 自己选的区域,这个是华北,别的自己搜一下
config.UseHttps = true; // 使用 HTTPS 进行上传
config.UseCdnDomains = true; // 使用 CDN 域名加速上传
config.ChunkSize = ChunkUnit.U512K; // 设置分块上传的块大小
// 实例化表单上传对象
FormUploader target = new FormUploader(config);
// 上传文件
HttpResult result = target.UploadStream(file.OpenReadStream(), saveKey, token, null); // 调用上传方法,上传文件流,传入保存路径和上传凭证
// 检查上传结果
if (result.Code == 200) // 判断上传是否成功(返回状态码200表示成功)
{
return Ok(file.FileName); // 返回200响应,上传成功,返回文件名
}
else
{
return BadRequest("上传失败:" + result.Text); // 如果上传失败,返回400错误响应,包含错误信息
}
}
```
-
多图片上传
/// <summary> /// 多图片上传 /// </summary> /// <param name="fileser"></param> /// <returns></returns> /// <exception cref="Exception"></exception> [HttpPost] public List<Object> UploadQiniu([FromForm(Name = "file")] List<IFormFile> fileser) { Mac mac = new Mac("AK", "SK"); // 替换为您七牛云账号的访问密钥和密钥 PutPolicy putPolicy = new PutPolicy(); putPolicy.Scope = "空间管理的名称";//图片存放七牛云地址 string token = Auth.CreateUploadToken(mac, putPolicy.ToJsonString()); // 生成上传凭证,凭证包含了上传策略信息 IFormFileCollection files = Request.Form.Files; Config config = new Config() { Zone = Zone.ZONE_CN_North,//默认华北 根据选择的不同输入不同的地区 UseHttps = true }; config.UseCdnDomains = true; // 使用 CDN 域名加速上传 var res = Request.Form.ToArray(); FormUploader upload = new FormUploader(config); HttpResult result = new HttpResult(); List<Object> list = new List<Object>(); foreach (IFormFile file in fileser)//获取多个文件列表集合 { if (file.Length > 0) { var _fileName = ContentDispositionHeaderValue .Parse(file.ContentDisposition) .FileName .Trim('"'); var _qiniuName = "Pic/" + DateTime.Now.ToString("yyyyMMddHHmmssffffff") + file.FileName;//重命名文件加上时间戳 Stream stream = file.OpenReadStream(); result = upload.UploadStream(stream, _qiniuName, token, null); if (result.Code == 200) { list.Add(new { fileName = _fileName, qiniuName = _qiniuName, uploadTime = DateTime.Now }); } else { throw new Exception(result.RefText);//上传失败错误信息 } } } return list; }
-
其中AK和SK的位置
- 然后就可以测试了
Linux Debian12 部署MySql 并建立外部连接
一、下载MySql
-
下载最新软件包。也可以在命令界面下使用下载最新的发行包。
wget https://repo.mysql.com/mysql-apt-config_0.8.29-1_all.deb
-
下载完成后,使用命令进行安装
dpkg -i mysql-apt-config_0.8.29-1_all.deb
-
执行完后会跳转到安装MySql配置界面
TAB进行保存,选中OK,然后按 Enter 键
-
使用命令更新软件包,并安装MySQL。
apt update apt install mysql-server
-
安装完成弹出密码框,输入两次密码相同即可,密码级别选择第一个即可
-
按照提示安装完成后,MySQL 服务会自动运行,我们可以通过命令查看服务运行状态:
systemctl status mysql
二、本地配置远程连接Linux中的MySQL
-
先登录MySQL
mysql -u root -p
-
创建一个用户名为 root 的用户,并授权其拥有所有数据库的所有权限(123456为密码)
create user root@'%' identified by '123456'; grant all privileges on *.* to root@'%' with grant option;
-
重新加载授权表
FLUSH PRIVILEGES;
-
查看用户权限
use mysql; select user,host from user;
-
退出mysql
exit;
-
找到mysqld.cnf 修改bind-adress为0.0.0.0
//一直返回到最高级文件夹 cd /etc/mysql/mysql.conf.d vi mysqld.cnf //添加 bind-address =0.0.0.0 //点击esc 输入 :wq 退出 :wq
-
重启MySql
service mysql restart
-
关闭防火墙
apt install firewalld systemctl stop firewalld
-
使用Navicat链接工具还是无法连接时
-
可以对你的客户端进行升级,如果您无法升级 MySQL 客户端,也可以尝试修改 MySQL 用户的身份验证方式,使其兼容旧版的客户端。您可以使用以下命令修改用户的身份验证方式:
//进入linux mysql mysql -u root -p //设置兼容旧版本(123456为密码) ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456'; //刷新权限 FLUSH PRIVILEGES;
-
重新连接
SignalR和WebSocket的区别
Cannot determine the frame size or a corrupted frame was received
HttpClient访问API的地址时,传输协议不匹配(https/http)
生成一维条形码
下载BarCodeLib包
/// <summary>
/// 一维码
/// </summary>
/// <param name="barcodeData"></param>
public static string UpdGenerateBarcodeImage(string barcodeData)
{
try
{
var filePath = Directory.GetCurrentDirectory() + @"\wwwroot\Pic\" + barcodeData + ".png";
var writer = new ZXing.Windows.Compatibility.BarcodeWriter()
{
Format = BarcodeFormat.CODE_128, // 例如 BarcodeFormat.CODE_128
Options = new ZXing.Common.EncodingOptions
{
Height = 100, // 你可以根据需要调整高度
Width = 300, // 你可以根据需要调整宽度
Margin = 2, // 边距
PureBarcode = false // 如果设置为true,则不包含文本
}
};
var barcodeBitmap = writer.Write(barcodeData); // 生成条形码图片
using (var file = new FileStream(filePath, FileMode.Create))
{
barcodeBitmap.Save(file, System.Drawing.Imaging.ImageFormat.Png);
}
return barcodeData + ".png";
}
catch (Exception ex)
{
Console.WriteLine("报错了:" + ex.Message);
throw;
}
}
Autofac拦截器 实现日志记录
[Intercept(typeof(LogInterceptor))]
public interface IService
{
}
//定义一个空标签指示拥有这个标签的方法不被 切入
public class NoNeedAop : Attribute
{
}
//接口里面打上标签 指示Get方法不被切入
public interface IServiceFactory : IService
{
//[TypeFilter(typeof(NoNeedAop))]
[NoNeedAop]
public Task<int> Get();
public Task<int> Post();
}
//修改拦截器判断是否存在标签
public class LogInterceptor : IInterceptor
{
/// <summary>
/// 实例化IInterceptor唯一方法
/// </summary>
/// <param name="invocation">包含被拦截方法的信息</param>
public void Intercept2(IInvocation invocation)
{
//存在这个标签就不拦截这个方法直接通过
//[TypeFilter(typeof(NoNeedAop))] 这种打标签的方法能拿到注入 但是判读取值比较麻烦
var IsHasAttrbute = invocation.Method.CustomAttributes.Any(a => a.ConstructorArguments.Any(x => (string)(((dynamic)x.Value).Name) == typeof(NoNeedAop).Name));
//[NoNeedAop] 这种打标签的方法 用下面这种取值方式
//var IsHasAttrbute = invocation.Method.CustomAttributes.Any(a => a.AttributeType.Name == typeof(NoNeedAop).Name);
if (!IsHasAttrbute)
{
Console.WriteLine("你正在调用方法 \"{0}\" 参数是 {1}... ",
invocation.Method.Name,
string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()));
invocation.Proceed();
Console.WriteLine("方法执行完毕,返回结果:{0}", invocation.ReturnValue);
}
else
{
invocation.Proceed();
}
}
/// <summary>
/// 实例化IInterceptor唯一方法
/// </summary>
/// <param name="invocation">包含被拦截方法的信息</param>
public void Intercept(IInvocation invocation)
{
Console.WriteLine("你正在调用方法 \"{0}\" 参数是 {1}... ",
invocation.Method.Name,
string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()));
invocation.Proceed();
Console.WriteLine("方法执行完毕,返回结果:{0}", invocation.ReturnValue);
}
}
Centos7更新Git到v2.+版本(默认1.8+)
Docker quick start 部署Apollo
.NET Aspire
dotnet workload install aspire 安装模板
dotnet new aspire 创建Aspire项目
标签:10,string,--,笔记,工作,key,new,上传,public From: https://www.cnblogs.com/keepingstudying/p/18236160