åè¨
æå¯è½æä¸å¹´æ²¡æä¹ç¢°C#äºï¼ç®åçå·¥ä½æ¯å¨å ¨èæå端ï¼æè¿ææ¶é´æ½ç©ºçäºä¸ä¸Asp.net Coreï¼Coreçæ¬å·é½å°äº5.0äºï¼ä¹è¶æ¥è¶å¥½ç¨äºï¼ä¸é¢å°è®°å½ä¸ä¸è¿å 天以æ¥ä½¿ç¨Asp.Net Core WebApi+Dapper+Mysql+Redis+Dockerçä¸æ¬¡å¼åè¿ç¨ã
项ç®ç»æ
æç»é¡¹ç®ç»æå¦ä¸ï¼CodeUin.Dapperæ°æ®è®¿é®å±,CodeUin.WebApiåºç¨å±ï¼å ¶ä¸æ¶åå°å ·ä½ä¸å¡é»è¾çæå°ç´æ¥åå¨Controllersä¸ï¼ä¸ååè¿å¤åå±ãCodeUin.Helpersæå°åæ¾ä¸äºé¡¹ç®çéç¨å¸®å©ç±»ï¼å¦ææ¯åªæ¶åå°å½åå±ç帮å©ç±»å°ç´æ¥å¨æå¨å±çº§ç§çHelpersæ件夹ä¸åå¨å³å¯ã
å®è£ ç¯å¢
MySQL
# ä¸è½½éå
docker pull mysql
# è¿è¡
docker run -itd --name 容å¨å称 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=ä½ çå¯ç mysql
Â
å¦ææ£å¨ä½¿ç¨ç客æ·ç«¯å·¥å ·è¿æ¥MySQLæ示1251ï¼è¿æ¯å 为客æ·ç«¯ä¸æ¯ææ°çå å¯æ¹å¼é æçï¼è§£å³åæ³å¦ä¸ã
# æ¥çå½åè¿è¡ç容å¨
docker ps
# è¿å
¥å®¹å¨
docker exec -it 容å¨å称 bash
# 访é®MySQL
mysql -u root -p
# æ¥çå å¯è§å
select host,user,plugin,authentication_string from mysql.user;
# 对è¿ç¨è¿æ¥è¿è¡ææ
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
# æ´æ¹å¯ç å å¯è§å
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'ä½ çå¯ç ';
# å·æ°æé
flush privileges;
æåï¼ä½¿ç¨MySQL客æ·ç«¯å·¥å ·è¿è¡è¿æ¥æµè¯ï¼æ使ç¨çå·¥å ·æ¯Navicat Premiumã
Redis
# ä¸è½½éå
docker pull redis
# è¿è¡
docker run -itd -p 6379:6379 redis
使ç¨Redis客æ·ç«¯å·¥å ·è¿è¡è¿æ¥æµè¯ï¼æ使ç¨çå·¥å ·æ¯Another Redis DeskTop Managerã
.NET ç¯å¢
æå¡å¨æ使ç¨çæ¯CentOS 8,使ç¨çNET SDKçæ¬5.0,ä¸é¢å°è®°å½ææ¯å¦ä½å¨CentOS 8ä¸å®è£ .NET SDKå.NETè¿è¡æ¶çã
# å®è£
SDK
sudo dnf install dotnet-sdk-5.0
# å®è£
è¿è¡æ¶
sudo dnf install aspnetcore-runtime-5.0
æ£æ¥æ¯å¦å®è£ æåï¼ä½¿ç¨
dotnet --info
å½ä»¤æ¥çå®è£ ä¿¡æ¯
å建项ç®
ä¸é¢å°å®ç°ä¸ä¸ªç¨æ·çç»å½æ³¨åï¼åè·åç¨æ·ä¿¡æ¯çå°åè½ã
æ°æ®æå¡å±
该å±è®¾è®¡åèäºÂ çé¾éªå±±Â çæ¶æï¼æä¹æ¯è¾å欢è¿ç§ç»æï¼ä¸çç»æå°±ç¥éæ¯è¦åä»ä¹çï¼ç®åæ¸ æ°ã
é¦å ï¼æ°å»ºä¸ä¸ªé¡¹ç®å½å为CodeUin.Dapperï¼åªç¨æ¥æä¾æ¥å£ï¼ä¸ºä¸å¡å±æå¡ã
- Entities
- åæ¾å®ä½ç±»
- IRepository
- åæ¾ä»åºæ¥å£
- Repository
- åæ¾ä»åºæ¥å£å®ç°ç±»
- BaseModel
- å®ä½ç±»çåºç±»ï¼ç¨æ¥åæ¾éç¨å段
- DataBaseConfig
- æ°æ®è®¿é®é 置类
- IRepositoryBase
- åæ¾æåºæ¬çä»å¨æ¥å£ å¢å æ¹æ¥ç
- RepositoryBase
- åºæ¬ä»å¨æ¥å£çå ·ä½å®ç°
å建BaseModelåºç±»
该类åæ¾å¨é¡¹ç®çæ ¹ç®å½ä¸ï¼ä¸»è¦ä½ç¨æ¯å°æ°æ®åºå®ä½ç±»ä¸é½æçå段ç¬ç«åºæ¥ã
1 using System;
2
3 namespace CodeUin.Dapper
4 {
5 /// <summary>
6 /// åºç¡å®ä½ç±»
7 /// </summary>
8 public class BaseModel
9 {
10 /// <summary>
11 /// 主é®Id
12 /// </summary>
13 public int Id { get; set; }
14
15 /// <summary>
16 /// å建æ¶é´
17 /// </summary>
18 public DateTime CreateTime { get; set; }
19 }
20 }
å建DataBaseConfigç±»
该类åæ¾å¨é¡¹ç®çæ ¹ç®å½ä¸ï¼æè¿é使ç¨çæ¯MySQLï¼éè¦å®è£ 以ä¸ä¾èµå ï¼å¦æ使ç¨çå ¶ä»æ°æ®åºï¼èªè¡å®è£ 对åºçä¾èµå å³å¯ã
è¯¥ç±»å ·ä½ä»£ç å¦ä¸ï¼
1 using MySql.Data.MySqlClient;
2 using System.Data;
3
4 namespace CodeUin.Dapper
5 {
6 public class DataBaseConfig
7 {
8 private static string MySqlConnectionString = @"Data Source=æ°æ®åºå°å;Initial Catalog=codeuin;Charset=utf8mb4;User ID=root;Password=æ°æ®åºå¯ç ;";
9
10 public static IDbConnection GetMySqlConnection(string sqlConnectionString = null)
11 {
12 if (string.IsNullOrWhiteSpace(sqlConnectionString))
13 {
14 sqlConnectionString = MySqlConnectionString;
15 }
16 IDbConnection conn = new MySqlConnection(sqlConnectionString);
17 conn.Open();
18 return conn;
19 }
20 }
21 }
å建IRepositoryBaseç±»
该类åæ¾å¨é¡¹ç®çæ ¹ç®å½ä¸ï¼åæ¾å¸¸ç¨çä»å¨æ¥å£ã
1 using System;
2 using System.Collections.Generic;
3 using System.Threading.Tasks;
4
5 namespace CodeUin.Dapper
6 {
7 public interface IRepositoryBase<T>
8 {
9 Task<int> Insert(T entity, string insertSql);
10
11 Task Update(T entity, string updateSql);
12
13 Task Delete(int Id, string deleteSql);
14
15 Task<List<T>> Select(string selectSql);
16
17 Task<T> Detail(int Id, string detailSql);
18 }
19 }
å建RepositoryBaseç±»
该类åæ¾å¨é¡¹ç®çæ ¹ç®å½ä¸ï¼æ¯IRepositoryBaseç±»çå ·ä½å®ç°ã
1 using Dapper;
2 using System.Collections.Generic;
3 using System.Data;
4 using System.Linq;
5 using System.Threading.Tasks;
6
7 namespace CodeUin.Dapper
8 {
9 public class RepositoryBase<T> : IRepositoryBase<T>
10 {
11 public async Task Delete(int Id, string deleteSql)
12 {
13 using (IDbConnection conn = DataBaseConfig.GetMySqlConnection())
14 {
15 await conn.ExecuteAsync(deleteSql, new { Id });
16 }
17 }
18
19 public async Task<T> Detail(int Id, string detailSql)
20 {
21 using (IDbConnection conn = DataBaseConfig.GetMySqlConnection())
22 {
23 return await conn.QueryFirstOrDefaultAsync<T>(detailSql, new { Id });
24 }
25 }
26
27 public async Task<List<T>> ExecQuerySP(string SPName)
28 {
29 using (IDbConnection conn = DataBaseConfig.GetMySqlConnection())
30 {
31 return await Task.Run(() => conn.Query<T>(SPName, null, null, true, null, CommandType.StoredProcedure).ToList());
32 }
33 }
34
35 public async Task<int> Insert(T entity, string insertSql)
36 {
37 using (IDbConnection conn = DataBaseConfig.GetMySqlConnection())
38 {
39 return await conn.ExecuteAsync(insertSql, entity);
40 }
41 }
42
43 public async Task<List<T>> Select(string selectSql)
44 {
45 using (IDbConnection conn = DataBaseConfig.GetMySqlConnection())
46 {
47 return await Task.Run(() => conn.Query<T>(selectSql).ToList());
48 }
49 }
50
51 public async Task Update(T entity, string updateSql)
52 {
53 using (IDbConnection conn = DataBaseConfig.GetMySqlConnection())
54 {
55 await conn.ExecuteAsync(updateSql, entity);
56 }
57 }
58 }
59 }
好äºï¼åºç¡ç±»åºæ¬å·²ç»å®ä¹å®æãä¸é¢å°æ°å»ºä¸ä¸ªUsersç±»ï¼å¹¶å®ä¹å 个常ç¨çæ¥å£ã
å建Userså®ä½ç±»
该类åæ¾å¨Entitiesæ件夹ä¸ï¼è¯¥ç±»ç»§æ¿BaseModelã
1 namespace CodeUin.Dapper.Entities
2 {
3 /// <summary>
4 /// ç¨æ·è¡¨
5 /// </summary>
6 public class Users : BaseModel
7 {
8 /// <summary>
9 /// ç¨æ·å
10 /// </summary>
11 public string UserName { get; set; }
12
13 /// <summary>
14 /// å¯ç
15 /// </summary>
16 public string Password { get; set; }
17
18 /// <summary>
19 /// ç
20 /// </summary>
21 public string Salt { get; set; }
22
23 /// <summary>
24 /// é®ç®±
25 /// </summary>
26 public string Email { get; set; }
27
28 /// <summary>
29 /// ææºå·
30 /// </summary>
31 public string Mobile { get; set; }
32
33 /// <summary>
34 /// æ§å«
35 /// </summary>
36 public int Gender { get; set; }
37
38 /// <summary>
39 /// å¹´é¾
40 /// </summary>
41 public int Age { get; set; }
42
43 /// <summary>
44 /// 头å
45 /// </summary>
46 public string Avatar { get; set; }
47
48 /// <summary>
49 /// æ¯å¦å é¤
50 /// </summary>
51 public int IsDelete { get; set; }
52 }
53 }
å建IUserRepositoryç±»
该类åæ¾å¨IRepositoryæ件夹ä¸ï¼ç»§æ¿IRepositoryBaseï¼å¹¶å®ä¹äºé¢å¤çæ¥å£ã
1 using CodeUin.Dapper.Entities;
2 using System;
3 using System.Collections.Generic;
4 using System.Threading.Tasks;
5
6 namespace CodeUin.Dapper.IRepository
7 {
8 public interface IUserRepository : IRepositoryBase<Users>
9 {
10 Task<List<Users>> GetUsers();
11
12 Task<int> AddUser(Users entity);
13
14 Task DeleteUser(int d);
15
16 Task<Users> GetUserDetail(int id);
17
18 Task<Users> GetUserDetailByEmail(string email);
19 }
20 }
å建UserRepositoryç±»
该类åæ¾å¨Repositoryæ件夹ä¸ï¼ç»§æ¿RepositoryBase, IUserRepository ,æ¯IUserRepositoryç±»çå ·ä½å®ç°ã
1 using CodeUin.Dapper.Entities;
2 using CodeUin.Dapper.IRepository;
3 using Dapper;
4 using System.Collections.Generic;
5 using System.Data;
6 using System.Threading.Tasks;
7
8 namespace CodeUin.Dapper.Repository
9 {
10 public class UserRepository : RepositoryBase<Users>, IUserRepository
11 {
12 public async Task DeleteUser(int id)
13 {
14 string deleteSql = "DELETE FROM [dbo].[Users] WHERE Id=@Id";
15 await Delete(id, deleteSql);
16 }
17
18
19 public async Task<Users> GetUserDetail(int id)
20 {
21 string detailSql = @"SELECT Id, Email, UserName, Mobile, Password, Age, Gender, CreateTime,Salt, IsDelete FROM Users WHERE Id=@Id";
22 return await Detail(id, detailSql);
23 }
24
25 public async Task<Users> GetUserDetailByEmail(string email)
26 {
27 string detailSql = @"SELECT Id, Email, UserName, Mobile, Password, Age, Gender, CreateTime, Salt, IsDelete FROM Users WHERE Email=@email";
28
29 using (IDbConnection conn = DataBaseConfig.GetMySqlConnection())
30 {
31 return await conn.QueryFirstOrDefaultAsync<Users>(detailSql, new { email });
32 }
33 }
34
35 public async Task<List<Users>> GetUsers()
36 {
37 string selectSql = @"SELECT * FROM Users";
38 return await Select(selectSql);
39 }
40
41 public async Task<int> AddUser(Users entity)
42 {
43 string insertSql = @"INSERT INTO Users (UserName, Gender, Avatar, Mobile, CreateTime, Password, Salt, IsDelete, Email) VALUES (@UserName, @Gender, @Avatar, @Mobile, now(),@Password, @Salt, @IsDelete,@Email);SELECT @id= LAST_INSERT_ID();";
44 return await Insert(entity, insertSql);
45 }
46 }
47 }
大ååæï¼æ¥ä¸æ¥éè¦æå¨å建æ°æ®åºå表ç»æï¼ä¸è½å使ç¨EFé£æ ·èªå¨çæäºï¼ä½¿ç¨Dapperåºæ¬ä¸æ¯è¦çº¯åSQLçï¼å¦ææ³åEFé£æ ·ä½¿ç¨ï¼å°±è¦é¢å¤çå®è£ ä¸ä¸ªæ©å±Â Dapper.Contribã
æ°æ®åºè¡¨ç»æå¦ä¸ï¼æ¯è¾ç®åã
DROP TABLE IF EXISTS `Users`;
CREATE TABLE `Users` (
`Id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主é®',
`Email` varchar(255) DEFAULT NULL COMMENT 'é®ç®±',
`UserName` varchar(20) DEFAULT NULL COMMENT 'ç¨æ·å称',
`Mobile` varchar(11) DEFAULT NULL COMMENT 'ææºå·',
`Age` int(11) DEFAULT NULL COMMENT 'å¹´é¾',
`Gender` int(1) DEFAULT '0' COMMENT 'æ§å«',
`Avatar` varchar(255) DEFAULT NULL COMMENT '头å',
`Salt` varchar(255) DEFAULT NULL COMMENT 'å ç',
`Password` varchar(255) DEFAULT NULL COMMENT 'å¯ç ',
`IsDelete` int(2) DEFAULT '0' COMMENT '0-æ£å¸¸ 1-å é¤',
`CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´',
PRIMARY KEY (`Id`),
UNIQUE KEY `USER_MOBILE_INDEX` (`Mobile`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8mb4 COMMENT='ç¨æ·ä¿¡æ¯è¡¨';
好äºï¼æ°æ®è®¿é®å±å¤§æ¦å°±è¿æ ·åäºï¼ä¸é¢æ¥ççåºç¨å±çå ·ä½å®ç°æ¹å¼ã
åºç¨ç¨åºå±
å建ä¸ä¸ªWebApi项ç®ï¼ä¸»è¦å¯¹å¤æä¾Apiæ¥å£æå¡ï¼å ·ä½ç»æå¦ä¸ã
- Autofac
- åæ¾IOC ä¾èµæ³¨å ¥çé 置项
- AutoMapper
- åæ¾å®ä½å¯¹è±¡æ å°å ³ç³»çé 置项
- Controllers
- æ§å¶å¨ï¼å ·ä½ä¸å¡é»è¾ä¹å°åå¨è¿
- Fliters
- åæ¾èªå®ä¹çè¿æ»¤å¨
- Helpers
- åæ¾æ¬å±ä¸ç¨å°çä¸äºå¸®å©ç±»
- Models
- åæ¾è¾å ¥/è¾åº/DTOçå®ä½ç±»
好äºï¼ç»æ大æ¦å°±æ¯è¿æ ·ãé误ä¼å ï¼å å¤çç¨åºå¼å¸¸ï¼åéææ¥å¿ç¨åºå§ã
èªå®ä¹å¼å¸¸å¤ç
å¨Helpersæ件夹ä¸å建ä¸ä¸ªErrorHandingMiddlewareä¸é´ä»¶ï¼æ·»å æ©å±æ¹æ³ErrorHandlingExtensionsï¼å¨Startupä¸å°ä¼ä½¿ç¨å°ã
1 using Microsoft.AspNetCore.Builder;
2 using Microsoft.AspNetCore.Http;
3 using Microsoft.Extensions.Logging;
4 using Newtonsoft.Json;
5 using System;
6 using System.Threading.Tasks;
7
8 namespace CodeUin.WebApi.Helpers
9 {
10 public class ErrorHandlingMiddleware
11 {
12 private readonly RequestDelegate next;
13 private readonly ILogger<ErrorHandlingMiddleware> _logger;
14
15 public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
16 {
17 this.next = next;
18 _logger = logger;
19 }
20
21 public async Task Invoke(HttpContext context)
22 {
23 try
24 {
25 await next(context);
26 }
27 catch (Exception ex)
28 {
29 _logger.LogError(ex.Message);
30
31 var statusCode = 500;
32
33 await HandleExceptionAsync(context, statusCode, ex.Message);
34 }
35 finally
36 {
37 var statusCode = context.Response.StatusCode;
38 var msg = "";
39
40 if (statusCode == 401)
41 {
42 msg = "æªææ";
43 }
44 else if (statusCode == 404)
45 {
46 msg = "æªæ¾å°æå¡";
47 }
48 else if (statusCode == 502)
49 {
50 msg = "请æ±é误";
51 }
52 else if (statusCode != 200)
53 {
54 msg = "æªç¥é误";
55 }
56 if (!string.IsNullOrWhiteSpace(msg))
57 {
58 await HandleExceptionAsync(context, statusCode, msg);
59 }
60 }
61 }
62
63 // å¼å¸¸é误信æ¯æè·ï¼å°é误信æ¯ç¨Jsonæ¹å¼è¿å
64 private static Task HandleExceptionAsync(HttpContext context, int statusCode, string msg)
65 {
66 var result = JsonConvert.SerializeObject(new { Msg = msg, Code = statusCode });
67
68 context.Response.ContentType = "application/json;charset=utf-8";
69
70 return context.Response.WriteAsync(result);
71 }
72 }
73
74 // æ©å±æ¹æ³
75 public static class ErrorHandlingExtensions
76 {
77 public static IApplicationBuilder UseErrorHandling(this IApplicationBuilder builder)
78 {
79 return builder.UseMiddleware<ErrorHandlingMiddleware>();
80 }
81 }
82 }
æåï¼å¨ Startup ç Configure æ¹æ³ä¸æ·»å app.UseErrorHandling() ï¼å½ç¨åºåéå¼å¸¸æ¶ï¼ä¼èµ°æ们çèªå®ä¹å¼å¸¸å¤çã
1 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
2 {
3 if (env.IsDevelopment())
4 {
5 app.UseDeveloperExceptionPage();
6 }
7
8 app.UseHttpsRedirection();
9
10 // 请æ±é误æ示é
ç½®
11 app.UseErrorHandling();
12
13 app.UseRouting();
14
15 app.UseAuthorization();
16
17 app.UseEndpoints(endpoints =>
18 {
19 endpoints.MapControllers();
20 });
21 }
æ¥å¿ç¨åº
æè¿é使ç¨çæ¯NLogï¼éè¦å¨é¡¹ç®ä¸å å®è£ ä¾èµå ã
é¦å å¨é¡¹ç®æ ¹ç®å½å建ä¸ä¸ª nlog.config çé ç½®æ件ï¼å ·ä½å 容å¦ä¸ã
1 <?xml version="1.0" encoding="utf-8" ?>
2 <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 autoReload="true"
5 internalLogLevel="Info"
6 internalLogFile="c:\temp\internal-nlog.txt">
7
8 <!-- enable asp.net core layout renderers -->
9 <extensions>
10 <add assembly="NLog.Web.AspNetCore"/>
11 </extensions>
12
13 <!-- the targets to write to -->
14 <targets>
15
16 <target xsi:type="File" name="allfile" fileName="${currentdir}\logs\nlog-all-${shortdate}.log"
17 layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${aspnet-request-ip}|${logger}|${message} ${exception:format=tostring}" />
18
19 <target xsi:type="Console" name="ownFile-web"
20 layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${aspnet-request-ip}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
21 </targets>
22 <!-- rules to map from logger name to target -->
23 <rules>
24 <!--All logs, including from Microsoft-->
25 <logger name="*" minlevel="Info" writeTo="allfile" />
26
27 <!--Skip non-critical Microsoft logs and so log only own logs-->
28 <logger name="Microsoft.*" maxlevel="Info" final="true" />
29 <!-- BlackHole without writeTo -->
30 <logger name="*" minlevel="Info" writeTo="ownFile-web" />
31 </rules>
32 </nlog>
æ´å¤é 置信æ¯å¯ä»¥ç´æ¥å»å®ç½æ¥ç https://nlog-project.org
æåï¼å¨ Program å ¥å£æ件ä¸éæ Nlog
1 using Autofac.Extensions.DependencyInjection;
2 using Microsoft.AspNetCore.Hosting;
3 using Microsoft.Extensions.Hosting;
4 using Microsoft.Extensions.Logging;
5 using NLog.Web;
6
7 namespace CodeUin.WebApi
8 {
9 public class Program
10 {
11 public static void Main(string[] args)
12 {
13 NLogBuilder.ConfigureNLog("nlog.config");
14 CreateHostBuilder(args).Build().Run();
15 }
16
17 public static IHostBuilder CreateHostBuilder(string[] args) =>
18 Host.CreateDefaultBuilder(args)
19 .UseServiceProviderFactory(new AutofacServiceProviderFactory())
20 .ConfigureLogging(logging =>
21 {
22 logging.ClearProviders();
23 logging.AddConsole();
24 })
25 .ConfigureWebHostDefaults(webBuilder =>
26 {
27 webBuilder.UseStartup<Startup>();
28 })
29 .UseNLog();
30 }
31 }
ç°å¨ï¼æ们å¯ä»¥ç´æ¥ä½¿ç¨NLogäºï¼ä½¿ç¨æ¹æ³å¯ä»¥æ¥çä¸é¢ç ErrorHandlingMiddleware ç±»ä¸æ使ç¨å°ã
ä¾èµæ³¨å ¥
å°ä½¿ç¨ Autofac æ¥ç®¡çç±»ä¹é´çä¾èµå ³ç³»ï¼Autofac æ¯ä¸æ¬¾è¶ 级èµç.NET IoC å®¹å¨ ãé¦å æ们éè¦å®è£ ä¾èµå ã
å¨ é¡¹ç®æ ¹ç®å½ç Autofac æ件夹ä¸æ°å»ºä¸ä¸ª CustomAutofacModule ç±»ï¼ç¨æ¥ç®¡çæ们类ä¹é´çä¾èµå ³ç³»ã
1 using Autofac;
2 using CodeUin.Dapper.IRepository;
3 using CodeUin.Dapper.Repository;
4
5 namespace CodeUin.WebApi.Autofac
6 {
7 public class CustomAutofacModule:Module
8 {
9 protected override void Load(ContainerBuilder builder)
10 {
11 builder.RegisterType<UserRepository>().As<IUserRepository>();
12 }
13 }
14 }
æåï¼å¨ Startup ç±»ä¸æ·»å æ¹æ³
1 public void ConfigureContainer(ContainerBuilder builder)
2 {
3 // ä¾èµæ³¨å
¥
4 builder.RegisterModule(new CustomAutofacModule());
5 }
å®ä½æ å°
å°ä½¿ç¨ Automapper 帮æ们解å³å¯¹è±¡æ å°å°å¦å¤ä¸ä¸ªå¯¹è±¡ä¸çé®é¢ï¼æ¯å¦è¿ç§ä»£ç ã
// å¦ææå å个å±æ§æ¯ç¸å½çå¯æç
var users = new Users
{
Email = user.Email,
Password = user.Password,
UserName = user.UserName
};
// 使ç¨Automapper就容æå¤äº
var model = _mapper.Map<Users>(user);
å å®è£ ä¾èµå
å¨é¡¹ç®æ ¹ç®å½ç AutoMapper æä»¶å¤¹ä¸ æ°å»º AutoMapperConfig ç±»ï¼æ¥ç®¡çæ们çæ å°å ³ç³»ã
1 using AutoMapper;
2 using CodeUin.Dapper.Entities;
3 using CodeUin.WebApi.Models;
4
5 namespace CodeUin.WebApi.AutoMapper
6 {
7 public class AutoMapperConfig : Profile
8 {
9 public AutoMapperConfig()
10 {
11 CreateMap<UserRegisterModel, Users>().ReverseMap();
12 CreateMap<UserLoginModel, Users>().ReverseMap();
13 CreateMap<UserLoginModel, UserModel>().ReverseMap();
14 CreateMap<UserModel, Users>().ReverseMap();
15 }
16 }
17 }
å¨ Startup æ件ç ConfigureServices æ¹æ³ä¸ æ·»å services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()) å³å¯ã
使ç¨JWT
ä¸é¢å°éæJWTï¼æ¥å¤çææçä¿¡æ¯ãé¦å ï¼éè¦å®è£ ä¾èµå ã
ä¿®æ¹ appsttings.json æ件ï¼æ·»å Jwt é 置信æ¯ã
1 {
2 "Logging": {
3 "LogLevel": {
4 "Default": "Information",
5 "Microsoft": "Warning",
6 "Microsoft.Hosting.Lifetime": "Information"
7 }
8 },
9 "AllowedHosts": "*",
10 "Jwt": {
11 "Key": "e816f4e9d7a7be785a", // è¿ä¸ªkeyå¿
须大äº16ä½æ°ï¼é常çæçæ¶åä¼æ¥é
12 "Issuer": "codeuin.com"
13 }
14 }
ç¶åå¨ Startup ç±»ç ConfigureServices æ¹æ³ä¸æ·»å Jwt ç使ç¨ã
1 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
2 .AddJwtBearer(options =>
3 {
4 options.TokenValidationParameters = new TokenValidationParameters
5 {
6 ValidateIssuer = true,
7 ValidateAudience = true,
8 ValidateLifetime = true,
9 ClockSkew = TimeSpan.FromMinutes(5), //ç¼å²è¿ææ¶é´ é»è®¤5åé
10 ValidateIssuerSigningKey = true,
11 ValidIssuer = Configuration["Jwt:Issuer"],
12 ValidAudience = Configuration["Jwt:Issuer"],
13 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
14 };
15 });
好äºï¼æç»æ们ç Startup ç±»æ¯è¿æ ·åçï¼å ³äºèªå®ä¹çåæ°éªè¯åé¢ä¼è®²å°ã
1 using Autofac;
2 using AutoMapper;
3 using CodeUin.WebApi.Autofac;
4 using CodeUin.WebApi.Filters;
5 using CodeUin.WebApi.Helpers;
6 using Microsoft.AspNetCore.Authentication.JwtBearer;
7 using Microsoft.AspNetCore.Builder;
8 using Microsoft.AspNetCore.Hosting;
9 using Microsoft.AspNetCore.Mvc;
10 using Microsoft.Extensions.Configuration;
11 using Microsoft.Extensions.DependencyInjection;
12 using Microsoft.Extensions.Hosting;
13 using Microsoft.IdentityModel.Tokens;
14 using System;
15 using System.Text;
16
17 namespace CodeUin.WebApi
18 {
19 public class Startup
20 {
21 public Startup(IConfiguration configuration)
22 {
23 Configuration = configuration;
24 }
25
26 public IConfiguration Configuration { get; }
27
28 public void ConfigureContainer(ContainerBuilder builder)
29 {
30 // ä¾èµæ³¨å
¥
31 builder.RegisterModule(new CustomAutofacModule());
32 }
33
34 // This method gets called by the runtime. Use this method to add services to the container.
35 public void ConfigureServices(IServiceCollection services)
36 {
37 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
38 .AddJwtBearer(options =>
39 {
40 options.TokenValidationParameters = new TokenValidationParameters
41 {
42 ValidateIssuer = true,
43 ValidateAudience = true,
44 ValidateLifetime = true,
45 ClockSkew = TimeSpan.FromMinutes(5), //ç¼å²è¿ææ¶é´ é»è®¤5åé
46 ValidateIssuerSigningKey = true,
47 ValidIssuer = Configuration["Jwt:Issuer"],
48 ValidAudience = Configuration["Jwt:Issuer"],
49 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
50 };
51 });
52
53 services.AddHttpContextAccessor();
54
55 // 使ç¨AutoMapper
56 services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
57
58 // å
³éåæ°èªå¨æ ¡éª
59 services.Configure<ApiBehaviorOptions>((options) =>
60 {
61 options.SuppressModelStateInvalidFilter = true;
62 });
63
64 // 使ç¨èªå®ä¹éªè¯å¨
65 services.AddControllers(options =>
66 {
67 options.Filters.Add<ValidateModelAttribute>();
68 }).
69 AddJsonOptions(options =>
70 {
71 // 忽ç¥nullå¼
72 options.JsonSerializerOptions.IgnoreNullValues = true;
73 });
74 }
75
76 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
77 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
78 {
79 if (env.IsDevelopment())
80 {
81 app.UseDeveloperExceptionPage();
82 }
83
84 app.UseHttpsRedirection();
85
86 // 请æ±é误æ示é
ç½®
87 app.UseErrorHandling();
88
89 // ææ
90 app.UseAuthentication();
91
92 app.UseRouting();
93
94 app.UseAuthorization();
95
96 app.UseEndpoints(endpoints =>
97 {
98 endpoints.MapControllers();
99 });
100 }
101 }
102 }
æ°å»ºå®ä½ç±»
æå°æ°å»ºä¸ä¸ªå®ä½ç±»ï¼åå«æ¯ UserLoginModel ç¨æ·ç»å½ï¼UserRegisterModel ç¨æ·æ³¨åï¼UserModel ç¨æ·åºæ¬ä¿¡æ¯ã
UserLoginModel å UserRegisterModel å°æ ¹æ®æ们å¨å±æ§ä¸é ç½®çç¹æ§èªå¨éªè¯åæ³æ§ï¼å°±ä¸éè¦å¨æ§å¶å¨ä¸åç¬åéªè¯é»è¾äºï¼æ大çèçäºå·¥ä½éã
1 using System;
2 using System.ComponentModel.DataAnnotations;
3
4 namespace CodeUin.WebApi.Models
5 {
6 /// <summary>
7 /// ç¨æ·å®ä½ç±»
8 /// </summary>
9 public class UserModel
10 {
11 public int Id { get; set; }
12
13 public string Email { get; set; }
14 public string UserName { get; set; }
15
16 public string Mobile { get; set; }
17
18 public int Gender { get; set; }
19
20 public int Age { get; set; }
21
22 public string Avatar { get; set; }
23 }
24
25 public class UserLoginModel
26 {
27 [Required(ErrorMessage = "请è¾å
¥é®ç®±")]
28 public string Email { get; set; }
29
30 [Required(ErrorMessage = "请è¾å
¥å¯ç ")]
31 public string Password { get; set; }
32 }
33
34 public class UserRegisterModel
35 {
36 [Required(ErrorMessage = "请è¾å
¥é®ç®±")]
37 [EmailAddress(ErrorMessage = "请è¾å
¥æ£ç¡®çé®ç®±å°å")]
38 public string Email { get; set; }
39
40 [Required(ErrorMessage = "请è¾å
¥ç¨æ·å")]
41 [MaxLength(length: 12, ErrorMessage = "ç¨æ·åæ大é¿åº¦ä¸è½è¶
è¿12")]
42 [MinLength(length: 2, ErrorMessage = "ç¨æ·åæå°é¿åº¦ä¸è½å°äº2")]
43 public string UserName { get; set; }
44
45 [Required(ErrorMessage = "请è¾å
¥å¯ç ")]
46 [MaxLength(length: 20, ErrorMessage = "å¯ç æ大é¿åº¦ä¸è½è¶
è¿20")]
47 [MinLength(length: 6, ErrorMessage = "å¯ç æå°é¿åº¦ä¸è½å°äº6")]
48 public string Password { get; set; }
49 }
50 }
éªè¯å¨
å¨é¡¹ç®æ ¹ç®å½ç Filters æä»¶å¤¹ä¸ æ·»å ValidateModelAttribute æ件夹ï¼å°å¨ Action 请æ±ä¸å è¿å ¥æ们çè¿æ»¤å¨ï¼å¦æä¸ç¬¦åæ们å®ä¹çè§åå°ç´æ¥è¾åºé误项ã
å ·ä½ä»£ç å¦ä¸ã
1 using Microsoft.AspNetCore.Mvc;
2 using Microsoft.AspNetCore.Mvc.Filters;
3 using System.Linq;
4
5 namespace CodeUin.WebApi.Filters
6 {
7 public class ValidateModelAttribute : ActionFilterAttribute
8 {
9 public override void OnActionExecuting(ActionExecutingContext context)
10 {
11 if (!context.ModelState.IsValid)
12 {
13 var item = context.ModelState.Keys.ToList().FirstOrDefault();
14
15 //è¿å第ä¸ä¸ªéªè¯åæ°é误çä¿¡æ¯
16 context.Result = new BadRequestObjectResult(new
17 {
18 Code = 400,
19 Msg = context.ModelState[item].Errors[0].ErrorMessage
20 });
21 }
22 }
23 }
24 }
æ·»å èªå®ä¹éªè¯ç¹æ§
ææ¶åæ们éè¦èªå·±é¢å¤çæ©å±ä¸äºè§åï¼åªéè¦ç»§æ¿ ValidationAttribute ç±»ç¶åå®ç° IsValid æ¹æ³å³å¯ï¼æ¯å¦æè¿ééªè¯äºä¸å½çææºå·ç ã
1 using System.ComponentModel.DataAnnotations;
2 using System.Text.RegularExpressions;
3
4 namespace CodeUin.WebApi.Filters
5 {
6 public class ChineMobileAttribute : ValidationAttribute
7 {
8 public override bool IsValid(object value)
9 {
10 if (!(value is string)) return false;
11
12 var val = (string)value;
13
14 return Regex.IsMatch(val, @"^[1]{1}[2,3,4,5,6,7,8,9]{1}\d{9}$");
15 }
16 }
17 }
å®ç°ç»å½æ³¨å
æ们æ¥å®ç°ä¸ä¸ªç®åçä¸å¡éæ±ï¼ç¨æ·æ³¨åï¼ç»å½ï¼åè·åç¨æ·ä¿¡æ¯ï¼å ¶ä»çåè½é½å¤§åå°å¼ï¼æ éå°±æ¯CRUDï¼ã
æ¥å£æ们å¨æ°æ®æå¡å±å·²ç»å好äºï¼æ¥ä¸æ¥æ¯å¤çä¸å¡é»è¾çæ¶åå°äºï¼å°ç´æ¥å¨ Controllers ä¸ç¼åã
æ°å»ºä¸ä¸ªæ§å¶å¨ UsersController ï¼ä¸å¡å¾ç®åï¼ä¸è¿å¤ä»ç»äºï¼å ·ä½ä»£ç å¦ä¸ã
1 using System;
2 using System.IdentityModel.Tokens.Jwt;
3 using System.Security.Claims;
4 using System.Text;
5 using System.Threading.Tasks;
6 using AutoMapper;
7 using CodeUin.Dapper.Entities;
8 using CodeUin.Dapper.IRepository;
9 using CodeUin.Helpers;
10 using CodeUin.WebApi.Models;
11 using Microsoft.AspNetCore.Authorization;
12 using Microsoft.AspNetCore.Http;
13 using Microsoft.AspNetCore.Mvc;
14 using Microsoft.Extensions.Configuration;
15 using Microsoft.Extensions.Logging;
16 using Microsoft.IdentityModel.Tokens;
17
18 namespace CodeUin.WebApi.Controllers
19 {
20 [Route("api/[controller]/[action]")]
21 [ApiController]
22 [Authorize]
23 public class UsersController : Controller
24 {
25 private readonly ILogger<UsersController> _logger;
26 private readonly IUserRepository _userRepository;
27 private readonly IMapper _mapper;
28 private readonly IConfiguration _config;
29 private readonly IHttpContextAccessor _httpContextAccessor;
30
31 public UsersController(ILogger<UsersController> logger, IUserRepository userRepository, IMapper mapper, IConfiguration config, IHttpContextAccessor httpContextAccessor)
32 {
33 _logger = logger;
34 _userRepository = userRepository;
35 _mapper = mapper;
36 _config = config;
37 _httpContextAccessor = httpContextAccessor;
38 }
39
40 [HttpGet]
41 public async Task<JsonResult> Get()
42 {
43 var userId = int.Parse(_httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value);
44
45 var userInfo = await _userRepository.GetUserDetail(userId);
46
47 if (userInfo == null)
48 {
49 return Json(new { Code = 200, Msg = "æªæ¾å°è¯¥ç¨æ·çä¿¡æ¯" });
50 }
51
52 var outputModel = _mapper.Map<UserModel>(userInfo);
53
54 return Json(new { Code = 200, Data = outputModel }); ;
55 }
56
57 [HttpPost]
58 [AllowAnonymous]
59 public async Task<JsonResult> Login([FromBody] UserLoginModel user)
60 {
61 // æ¥è¯¢ç¨æ·ä¿¡æ¯
62 var data = await _userRepository.GetUserDetailByEmail(user.Email);
63
64 // è´¦å·ä¸åå¨
65 if (data == null)
66 {
67 return Json(new { Code = 200, Msg = "è´¦å·æå¯ç é误" });
68 }
69
70 user.Password = Encrypt.Md5(data.Salt + user.Password);
71
72 // å¯ç ä¸ä¸è´
73 if (!user.Password.Equals(data.Password))
74 {
75 return Json(new { Code = 200, Msg = "è´¦å·æå¯ç é误" });
76 }
77
78 var userModel = _mapper.Map<UserModel>(data);
79
80 // çætoken
81 var token = GenerateJwtToken(userModel);
82
83 // åå
¥Redis
84 await new RedisHelper().StringSetAsync($"token:{data.Id}", token);
85
86 return Json(new
87 {
88 Code = 200,
89 Msg = "ç»å½æå",
90 Data = userModel,
91 Token = token
92 });
93 }
94
95 [HttpPost]
96 [AllowAnonymous]
97 public async Task<JsonResult> Register([FromBody] UserRegisterModel user)
98 {
99 // æ¥è¯¢ç¨æ·ä¿¡æ¯
100 var data = await _userRepository.GetUserDetailByEmail(user.Email);
101
102 if (data != null)
103 {
104 return Json(new { Code = 200, Msg = "该é®ç®±å·²è¢«æ³¨å" });
105 }
106
107 var salt = Guid.NewGuid().ToString("N");
108
109 user.Password = Encrypt.Md5(salt + user.Password);
110
111 var users = new Users
112 {
113 Email = user.Email,
114 Password = user.Password,
115 UserName = user.UserName
116 };
117
118 var model = _mapper.Map<Users>(user);
119
120 model.Salt = salt;
121
122 await _userRepository.AddUser(model);
123
124 return Json(new { Code = 200, Msg = "注åæå" });
125 }
126
127 /// <summary>
128 /// çæToken
129 /// </summary>
130 /// <param name="user">ç¨æ·ä¿¡æ¯</param>
131 /// <returns></returns>
132 private string GenerateJwtToken(UserModel user)
133 {
134 var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
135 var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
136
137 var claims = new[] {
138 new Claim(JwtRegisteredClaimNames.Email, user.Email),
139 new Claim(JwtRegisteredClaimNames.Gender, user.Gender.ToString()),
140 new Claim(ClaimTypes.NameIdentifier,user.Id.ToString()),
141 new Claim(ClaimTypes.Name,user.UserName),
142 new Claim(ClaimTypes.MobilePhone,user.Mobile??""),
143 };
144
145 var token = new JwtSecurityToken(_config["Jwt:Issuer"],
146 _config["Jwt:Issuer"],
147 claims,
148 expires: DateTime.Now.AddMinutes(120),
149 signingCredentials: credentials);
150
151 return new JwtSecurityTokenHandler().WriteToken(token);
152 }
153 }
154 }
æ¥ä¸æ¥æµè¯ä¸ä¸æ们çåè½ï¼é¦å æ¯æ³¨åã
å æ¥éªè¯ä¸ä¸æ们çä¼ å ¥çåæ°æ¯å¦ç¬¦åæ们å®ä¹çè§åã
è¾å ¥ä¸ä¸ªé误çé®ç®±å·è¯è¯çï¼
okï¼æ²¡æé®é¢ï¼åæä»¬å¨ UserRegisterModel ä¸ æ·»å çéªè¯ç¹æ§è¿åç»æä¸è´ï¼æåæ们æµè¯ä¸ä¸å®å ¨ç¬¦åè§åçæ åµã
æåï¼æ³¨åæåäºï¼æ¥è¯¢ä¸æ°æ®åºä¹æ¯åå¨çã
æ们æ¥è¯è¯ç»å½æ¥å£ï¼å¨è°ç¨ç»å½æ¥å£ä¹åæ们å æ¥æµè¯ä¸ä¸æ们çé ç½®çæééªè¯æ¯å¦å·²ç»çæï¼å¨ä¸ç»å½çæ åµä¸ç´æ¥è®¿é®è·åç¨æ·ä¿¡æ¯æ¥å£ã
ç´æ¥è®¿é®ä¼è¿åæªææï¼é£æ¯å 为æ们没æç»å½ï¼èªç¶ä¹å°±æ²¡æ Tokenï¼ç®åæ¥çæ¯æ²¡é®é¢çï¼ä½è¦ççæä»¬ä¼ å ¥æ£ç¡®çToken æ¯å¦è½è¿æééªè¯ã
ç°å¨ï¼æ们éè¦è°ç¨ç»å½æ¥å£ï¼ç»å½æååä¼è¿åä¸ä¸ªTokenï¼åé¢çæ¥å£è¯·æ±é½éè¦ç¨å°ï¼ä¸ç¶ä¼æ æé访é®ã
å æ¥æµè¯ä¸ä¸å¯ç é误çæ åµã
è¿åæ£ç¡®ï¼ç¬¦åæ们çé¢æç»æï¼ä¸é¢å°è¯è¯æ£ç¡®çå¯ç ç»å½ï¼çæ¯å¦è½å¤è¿åæ们æ³è¦çç»æã
ç»å½æåï¼æ¥å£ä¹è¿åäºæ们é¢æçç»æï¼æåçççæç token æ¯å¦æç §æ们åçé»è¾é£æ ·ï¼åä¸ä»½å° redis å½ä¸ã
ä¹æ¯æ²¡æé®é¢çï¼åæ们é¢æ³çä¸æ ·ã
ä¸é¢å°æºå¸¦æ£ç¡®ç token 请æ±è·åç¨æ·ä¿¡æ¯çæ¥å£ï¼ççæ¯å¦è½å¤æ£ç¡®è¿åã
è·åç¨æ·ä¿¡æ¯çæ¥å£ä¸ä¼æºå¸¦ä»»ä½åæ°ï¼åªä¼å¨è¯·æ±å¤´ç Headers ä¸ æ·»å Authorization ï¼å°æ们æ£ç¡®ç token ä¼ å ¥å ¶ä¸ã
è½å¤æ£ç¡®è·åå°æ们çç¨æ·ä¿¡æ¯ï¼ä¹å°±æ¯è¯´æ们çæéè¿ä¸åä¹æ¯æ²¡æé®é¢çäºï¼ä¸é¢å°ä½¿ç¨ Docker æå é¨ç½²å° Linux æå¡å¨ä¸ã
æå é¨ç½²
å¨é¡¹ç®çæ ¹ç®å½ä¸æ·»å Dockerfile æ件ï¼å 容å¦ä¸ã
1 #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
2
3 FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
4 WORKDIR /app
5 EXPOSE 80
6 EXPOSE 443
7
8 FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build
9 WORKDIR /src
10 COPY ["CodeUin.WebApi/CodeUin.WebApi.csproj", "CodeUin.WebApi/"]
11 COPY ["CodeUin.Helpers/CodeUin.Helpers.csproj", "CodeUin.Helpers/"]
12 COPY ["CodeUin.Dapper/CodeUin.Dapper.csproj", "CodeUin.Dapper/"]
13 RUN dotnet restore "CodeUin.WebApi/CodeUin.WebApi.csproj"
14 COPY . .
15 WORKDIR "/src/CodeUin.WebApi"
16 RUN dotnet build "CodeUin.WebApi.csproj" -c Release -o /app/build
17
18 FROM build AS publish
19 RUN dotnet publish "CodeUin.WebApi.csproj" -c Release -o /app/publish
20
21 FROM base AS final
22 WORKDIR /app
23 COPY --from=publish /app/publish .
24 ENTRYPOINT ["dotnet", "CodeUin.WebApi.dll"]
å¨ Dockerfile æ件çç®å½ä¸è¿è¡æå å½ä»¤
# å¨å½åæ件夹ï¼æ«å°¾çå¥ç¹ï¼ä¸æ¥æ¾ Dockerfile
docker build -t codeuin-api .
# æ¥çéå
docker images
# ä¿åéåå°æ¬å°
docker save -o codeuin-api.tar codeuin-api
æåï¼å°æ们ä¿åçéåéè¿ä¸ä¼ çæå¡å¨åå¯¼å ¥å³å¯ã
éè¿ ssh å½ä»¤ è¿æ¥æå¡å¨ï¼å¨åä¸ä¼ å çç®å½ä¸æ§è¡å¯¼å ¥å½ä»¤ã
# å è½½éå
docker load -i codeuin-api.tar
# è¿è¡éå
docker run -itd -p 8888:80 --name codeuin-api codeuin-api
# æ¥çè¿è¡ç¶æ
docker stats
å°æ¤ä¸ºæ¢ï¼æ们æ´ä¸ªé¨ç½²å·¥ä½å·²ç»å®æäºï¼æåå¨è¯·æ±æå¡å¨çæ¥å£æµè¯ä¸ä¸æ¯å¦okã
æç»çç»æä¹æ¯okçï¼å°æ¤ä¸ºæ¢ï¼æ们ææåºç¡çå·¥ä½é½å®æäºï¼ææç代ç åå¨å¨Â  https://github.com/xiazanzhang/dotnet5 ä¸ï¼å¦æå¯¹ä½ æ帮å©çè¯å¯ä»¥åèä¸ä¸ã