Abp Vnext Pro ç Vue3 å®ç°çæ¬ å¼ç®±å³ç¨çä¸åå°å端/设计解å³æ¹æ¡
å¼å§
- Githubå°å
- ææ¡£å°å
- æ¼ç¤ºå°å
ç³»ç»åè½
- ç¨æ·ç®¡ç
- è§è²ç®¡ç
- 审计æ¥å¿
- åå°ä»»å¡
- éæäºä»¶
- IdentityServer4
- 客æ·ç«¯ç®¡ç
- Api èµæºç®¡ç
- ApiScope 管ç
- Identity èµæºç®¡ç
- SinglaR æ¶æ¯éç¥
- å¤è¯è¨
- FreeSql
- æ°æ®åå ¸(UI ææ¶æ²¡æ)
- 容å¨åé¨ç½²
- åå æµè¯
- ES æ¥å¿
- Setting 管ç
- å¤ç§æ·
- ç»ç»æºæ
项ç®ç»æ
å端
.
âââ Directory.Build.props nuget çæ¬æ§å¶
âââ frameworks # å
Œ
±æ¨¡å
â âââ CAP # dotnetcore.cap
â âââ Extensions # èªå®ä¹æ©å±
âââ gateways # ç½å
³
âââ modules # 模å
â âââ DataDictionaryManagement # æ°æ®åå
¸
â âââ NotificationManagement # éç¥æå¡
âââ services # å
Œ
±éæèµæºç®å½
â âââ host # å¯å¨æ¨¡å
â âââ CompanyName.ProjectName.HttpApi.Host # admin ui host
â âââ CompanyName.ProjectName.IdentityServer # IdentityServer host
â âââ src # æºç
â âââ CompanyName.ProjectName.DbMigrator # è¿ç§»æ§å¶å°ç¨åº
â âââ test # åå
æµè¯
å端
.
âââ _nginx # docker æå
âââ build # æå
èæ¬ç¸å
³
â âââ config # é
ç½®æ件
â âââ generate # çæå¨
â âââ script # èæ¬
â âââ vite # viteé
ç½®
âââ mock # mockæ件夹
âââ public # å
Œ
±éæèµæºç®å½
âââ src # 主ç®å½
â âââ api # æ¥å£æ件
â âââ assets # èµæºæ件
â â âââ icons # icon sprite å¾æ æ件夹
â â âââ images # 项ç®åæ¾å¾ççæ件夹
â â âââ svg # 项ç®åæ¾svgå¾ççæ件夹
â âââ components # å
Œ
±ç»ä»¶
â âââ design # æ ·å¼æ件
â âââ directives # æ令
â âââ enums # æ举/常é
â âââ hooks # hook
â â âââ component # ç»ä»¶ç¸å
³hook
â â âââ core # åºç¡hook
â â âââ event # äºä»¶ç¸å
³hook
â â âââ setting # é
ç½®ç¸å
³hook
â â âââ web # webç¸å
³hook
â âââ layouts # å¸å±æ件
â â âââ default # é»è®¤å¸å±
â â âââ iframe # iframeå¸å±
â â âââ page # 页é¢å¸å±
â âââ locales # å¤è¯è¨
â âââ logics # é»è¾
â âââ main.ts # 主å
¥å£
â âââ router # è·¯ç±é
ç½®
â âââ services # Nswagçæç代ç
â â âââ ServiceProxies.ts # Nswagçæç代ç
â â âââ ServiceProxyBase.ts # Nswagçæç代çæ¦æªå¨
â âââ settings # 项ç®é
ç½®
â â âââ componentSetting.ts # ç»ä»¶é
ç½®
â â âââ designSetting.ts # æ ·å¼é
ç½®
â â âââ encryptionSetting.ts # å å¯é
ç½®
â â âââ localeSetting.ts # å¤è¯è¨é
ç½®
â â âââ projectSetting.ts # 项ç®é
ç½®
â â âââ siteSetting.ts # ç«ç¹é
ç½®
â âââ store # æ°æ®ä»åº
â âââ utils # å·¥å
·ç±»
â âââ views # 页é¢
âââ test # æµè¯
â âââ server # æµè¯ç¨å°çæå¡
â âââ api # æµè¯æå¡å¨
â âââ upload # æµè¯ä¸ä¼ æå¡å¨
â âââ websocket # æµè¯wsæå¡å¨
âââ types # ç±»åæ件
âââ vite.config.ts # viteé
ç½®æ件
âââ windi.config.ts # windcssé
ç½®æ件
è¿è¡é¡¹ç®åæ
- Mysql
docker run --name mymysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=1q2w3E* -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
- Redis
docker run --name myredis -p 6379:6379 -d redis:latest redis-server
- RabbitMq éå¿ é¡»
- appsetting.development.json-> CAP:Enabled 设置为 false
docker run -d --name myrabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 rabbitmq:management
- ELK éå¿ é¡»
- appsetting.development.json-> LogToElasticSearch:Enabled 设置为 false
- å®è£ Node.js, Npm Or Yarn
è·å项ç®
- ç´æ¥ clone 项ç®
git clone https://github.com/WangJunZzz/abp-vnext-pro.git
OR
- ä¸è½½ä»£ç çæå¨
git clone https://github.com/WangJunZzz/abp-vnext-pro-gui.git
- ä¸è½½ä»£ç çæçæå¨ä¹åï¼è¾å ¥èªå·±æ³è¦ç项ç®å称çæ代ç å³å¯
å¯å¨
- ä¿®æ¹ HttpApi.Host-> appsettings.development.json çæ°æ®åºè¿æ¥å符串ï¼Redis, RabbitMq,Es å°åå³å¯(å¦æ没æ es ä¹å¯ä»¥è¿è¡,åªæ¯å端 es æ¥å¿é¡µé¢æ æ³ä½¿ç¨èå·²ï¼ä¸å½±åå端项ç®å¯å¨)
- ä¿®æ¹ IdentityServer-> appsettings.development.json æ°æ®åºè¿æ¥å符串
- ä¿®æ¹ DbMigrator-> appsettings.json æ°æ®åºè¿æ¥å符串
- è¿è¡ DbMigrator çææ°æ®åº
- å¯å¨ HttpApi.Host å IdentityServer
- å端 yarn ä¹åï¼æ§è¡ npm run dev å¯å¨
é 置说æ
- HttpApi.Host-> appsettings.development.json
{
// Serilog æ¥å¿é
ç½®ï¼çæç¯å¢ä¿®æ¹æ¥å¿çº§å«
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"Volo.Abp": "Information",
"Hangfire": "Information",
"DotNetCore.CAP": "Information",
"Serilog.AspNetCore": "Information"
}
}
},
// è·¨å设置
"App": {
"CorsOrigins": "https://*.ProjectName.com,http://localhost:4200,http://localhost:3100"
},
// æ°æ®åºè¿æ¥å符串ï¼ä¿®æ¹ä¸ºä½ æ¬å°çmysqlå°å
"ConnectionStrings": {
"Default": "Data Source=localhost;Database=CompanyNameProjectNameDB;uid=root;pwd=1q2w3E*;charset=utf8mb4;Allow User Variables=true;AllowLoadLocalInfile=true"
},
// Redisç¼å
"Cache": {
"Redis": {
"ConnectionString": "localhost",
"Password": "mypassword",
"DatabaseId": 0
}
},
// Jwté
ç½®
"Jwt": {
"Audience": "CompanyNameProjectName",
//客æ·ç«¯æ è¯
"SecurityKey": "dzehzRz9a8asdfasfdadfasdfasdfafsdadfasbasdf=",
"Issuer": "CompanyNameProjectName",
//ç¾åè
"ExpirationTime": 24
//è¿ææ¶é´ hour
},
// 使ç¨äºDotnetcore.capçrabbitmq,falseçæ
åµåºäºå
å
"Cap": {
"Enabled": "false",
"RabbitMq": {
"HostName": "localhost",
"UserName": "admin",
"Password": "admin"
}
},
// esæ¥å¿å°åé
ç½®
"LogToElasticSearch": {
"Enabled": "true",
"ElasticSearch": {
"Url": "http://es.cn",
"IndexFormat": "companyname.projectname.development",
"UserName": "elastic",
"Password": "aVVhjQ95RP7nbwNy",
"DashboardIndex": "companyname.projectname"
}
},
// identityserverå°å
"HttpClient": {
"Sts": {
"Url": "http://localhost:44354"
}
},
// Consul æå¡åç°åæ²»ç
"Consul": {
"Enabled": false,
"Host": "http://localhost:8500",
"Service": "Project-Service"
}
}
- IdentityServer-> appsettings.development.json
{
"App": {
"SelfUrl": "https://localhost:44354",
"ClientUrl": "http://localhost:4200",
"CorsOrigins": "https://*.ProjectName.com,http://localhost:4200,https://localhost:44307,https://localhost:44315",
"RedirectAllowedUrls": "http://localhost:4200,https://localhost:44307"
},
// mysqlè¿æ¥å符串
"ConnectionStrings": {
"Default": "Data Source=localhost;Database=CompanyNameProjectNameDB;uid=root;pwd=1q2w3E*;charset=utf8mb4;Allow User Variables=true;AllowLoadLocalInfile=true"
},
// Redis
"Redis": {
"Configuration": "localhost,password=mypassword"
}
}
- DbMigrator-> appsettings.json
// è¿ç§»æ°æ®åº
"ConnectionStrings": {
"Default": "Data Source=localhost;Database=CompanyNameProjectNameDB;uid=root;pwd=1q2w3E*;charset=utf8mb4;Allow User Variables=true;AllowLoadLocalInfile=true"
}
- å端éç¨ TypeScript,ææçç±»åå¨æçæ NSwag
- å端 api ç»ä¸ä½¿ç¨ Post
- å®ä¹ api æ ¼å¼
// ä¸å®è¦æTagsï¼å 为å端ä¼æ ¹æ®è¿ä¸ªçæ代çç±»
// 建议åæ°é½å°è£
为ä¸ä¸ªInput
[SwaggerOperation(summary: "ç»å½", Tags = new[] {"Account"})]
public Task<LoginOutput> LoginAsync(LoginInput input)
{
return _loginAppService.LoginAsync(input);
}
- å¨å端ç®å½ä¸é
置代ççå°å
- nswag->nswag.json
"documentGenerator": {
"fromDocument": {
"url": "http://localhost:44315/swagger/v1/swagger.json", // 代çå°åï¼åªæçæçæ¶åç¨ï¼ä¸åºåç¯å¢
}
}
- å¦ææ¥å£åæ°æè è¿åå¼ææ¹åï¼éè¦éæ°çæ代çï¼æ§è¡:
npm run nswag
- å端å¤ç¯å¢ï¼.env.development å.env.production
- æ¥å£å°åé ç½® VITE_API_URL
- IdentityServer å°åé ç½® VITE_AUTH_URL
- æéé ç½®
- èåæé
- src/router/routes
policy å段å¹é å端çæéå称
- æé®æé
v-auth="'AbpIdentity.Users.Delete'"
- src/router/routes
å¥åº·æ£æ¥
模å
- æä¾åå§ç»å½å第ä¸æ¹ç»å½(IdentityServer4),é»è®¤ç¨æ·åå¯ç ï¼admin 1q2w3*
- æéå®ä¹(Application.Contracts å±)
- Abp ä¼èªå¨æ«æç»§æ¿ PermissionDefinitionProvider
- ææ¡£ Abp å®æ¹
- å¨ Http.Api ç Controller æä¸ Authorize
设置管ç
- éæAbp.SettingUi
æ¶æ¯éç¥
- æ¶æ¯ç±»åï¼åéç»æå®äººå广ææ¶æ¯
- åéæ¶æ¯å°å端ï¼éè¿éæäºä»¶å RabbitMq
- æ³¨å ¥ NotificationManager åéæ¶æ¯ï¼
/// <summary>
/// åéæ®éææ¬æ¶æ¯
/// </summary>
/// <returns></returns>
/// <exception cref="NotificationManagementDomainException"></exception>
public async Task<Notification> SendCommonTextAsync(string title, string content, List<Guid> receiveIds)
{
if (receiveIds is {Count: 0})
{
throw new NotificationManagementDomainException("æ¶æ¯æ¥æ¶äººä¸è½ä¸ºç©º");
var senderId = Guid.Empty;
if (_currentUser?.Id != null)
{
senderId = _currentUser.Id.Value;
var entity = new Notification(GuidGenerator.Create(), title, content, MessageType.Text, senderId);
foreach (var item in receiveIds)
{
entity.AddNotificationSubscription(GuidGenerator.Create(), item);
var notificationEto = ObjectMapper.Map<Notification, NotificationEto>(entity);
// åééæäºä»¶
entity.AddCreatedNotificationDistributedEvent(new CreatedNotificationDistributedEvent(notificationEto));
return entity = await _notificationRepository.InsertAsync(entity);
}
- Handler å½åäºä»¶:NotificationCreatedDistributedEventHandler
/// <summary>
/// åéæ¶æ¯
/// </summary>
public async Task SendMessageAsync(string title, string content, MessageType messageType, List<string> users)
{
switch (messageType)
{
case MessageType.Text:
await SendMessageToClientByUserIdAsync(new SendNotificationDto(title, content, messageType), users);
break;
case MessageType.BroadCast:
await SendMessageToAllClientAsync(new SendNotificationDto(title, content, messageType));
break;
default:
throw new UserFriendlyException("æªç¥çæ¶æ¯ç±»å");
}
}
- å端æ¥å SignalR æ¶æ¯
// src/hooks/web/useSignalR.js
import * as signalR from "@microsoft/signalr";
import { useMessage } from "/@/hooks/web/useMessage";
import { useUserStoreWithOut } from "/@/store/modules/user";
export function useSignalR() {
/**
* å¼å§è¿æ¥SignalR
*/
function startConnect(): void {
let connection = connectionsignalR();
//æ¥æ¶æ®éææ¬æ¶æ¯
connection.on("ReceiveTextMessageAsync", ReceiveTextMessageHandlerAsync);
//æ¥æ¶å¹¿ææ¶æ¯
connection.on("ReceiveBroadCastMessageAsync", ReceiveBroadCastMessageHandlerAsync);
//å¼å§è¿æ¥
connection.start();
}
/**
* è¿æ¥signalr
*/
function connectionsignalR(): signalR.HubConnection {
const userStore = useUserStoreWithOut();
const token = userStore.getToken;
const url = (import.meta.env.VITE_WEBSOCKE_URL as string) + "/ws/signalr/notification";
const connection = new signalR.HubConnectionBuilder()
.withUrl(url, {
accessTokenFactory: () => token,
skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets,
})
.withAutomaticReconnect({
nextRetryDelayInMilliseconds: (retryContext) => {
//éè¿è§åï¼éè¿æ¬¡æ°<300ï¼é´é1s;éè¯æ¬¡æ°<3000:é´é3s;éè¯æ¬¡æ°>3000:é´é30s
let count = retryContext.previousRetryCount / 300;
if (count < 1) {
//éè¯æ¬¡æ°<300,é´é1s
return 1000;
} else if (count < 10) {
//éè¯æ¬¡æ°>300:é´é5s
return 1000 * 5;
} //éè¯æ¬¡æ°>3000:é´é30s
else {
return 1000 * 30;
}
},
})
.configureLogging(signalR.LogLevel.Debug)
.build();
return connection;
}
/**
* æ¥æ¶ææ¬æ¶æ¯
* @param message æ¶æ¯ä½
*/
function ReceiveTextMessageHandlerAsync(message: any) {
console.log(message);
const { notification } = useMessage();
notification.open({
message: message.title,
description: message.content,
});
}
/**
* æ¥æ¶å¹¿ææ¶æ¯
* @param message æ¶æ¯ä½
*/
function ReceiveBroadCastMessageHandlerAsync(message: any) {
const { notification } = useMessage();
notification.open({
message: message.title,
description: message.content,
});
}
return { startConnect };
}
- åè Abp å®æ¹ææ¡£å³å¯
- å¨ appsetting.development.json 设置æ¯å¦å¼å¯
"LogToElasticSearch": {
"Enabled": "false", // å¦æ为fasel,æ¥å¿ä¹ä¼åå
¥å°æ¬å°ï¼å®è£
ELK,åèä¸é¢çdocker-compose
"ElasticSearch": {
"Url": "http://es.cn",
"IndexFormat": "companyname.projectname.development",
"UserName": "elastic",
"Password": "aVVhjQ95RP7nbwNy",
"DashboardIndex": "companyname.projectname"
}
},
- å®æ¶ä»»å¡
public override void OnPostApplicationInitialization(ApplicationInitializationContext context)
{
context.CreateRecurringJob();
base.OnPostApplicationInitialization(context);
}
- 延è¿ä»»å¡: å®æ¹ææ¡£
- éæ dotnetcore.CAP
"Cap": {
"Enabled": "false", //å¦æ为false é»è®¤ä½¿ç¨å
å级å«çéåï¼å¦å请å®è£
rabbitmq
"RabbitMq": {
"HostName": "localhost",
"UserName": "admin",
"Password": "admin"
}
},
private void ConfigurationCap(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var enabled = configuration.GetValue<bool>("Cap:Enabled", false);
if (enabled)
{
context.AddAbpCap(capOptions =>
{
capOptions.UseEntityFramework<ProjectNameDbContext>();
capOptions.UseRabbitMQ(option =>
{
option.HostName = configuration.GetValue<string>("Cap:RabbitMq:HostName");
option.UserName = configuration.GetValue<string>("Cap:RabbitMq:UserName");
option.Password = configuration.GetValue<string>("Cap:RabbitMq:Password");
});
var hostingEnvironment = context.Services.GetHostingEnvironment();
bool auth = !hostingEnvironment.IsDevelopment();
capOptions.UseDashboard(options => { options.UseAuth = auth; });
});
}
else
{
context.AddAbpCap(capOptions =>
{
capOptions.UseInMemoryStorage();
capOptions.UseInMemoryMessageQueue();
var hostingEnvironment = context.Services.GetHostingEnvironment();
bool auth = !hostingEnvironment.IsDevelopment();
capOptions.UseDashboard(options => { options.UseAuth = auth; });
});
}
}
- åå¸äºä»¶
- å¯åèéç¥æ¨¡å
// åééæäºä»¶
entity.AddCreatedNotificationDistributedEvent(new CreatedNotificationDistributedEvent(notificationEto));
- 订é
äºä»¶
/// <summary>
/// å建æ¶æ¯äºä»¶å¤ç
/// </summary>
public class
CreatedNotificationDistributedEventHandler : IDistributedEventHandler<CreatedNotificationDistributedEvent>,
ITransientDependency
{
private readonly INotificationAppService _hubAppService;
public CreatedNotificationDistributedEventHandler(INotificationAppService hubAppService)
{
_hubAppService = hubAppService;
}
public Task HandleEventAsync(CreatedNotificationDistributedEvent eventData)
{
return _hubAppService.SendMessageAsync(
eventData.NotificationEto.Title,
eventData.NotificationEto.Content,
eventData.NotificationEto.MessageType,
eventData.NotificationEto.NotificationSubscriptions.Select(e => e.ReceiveId.ToString()).ToList());
}
}
身份认è¯ä¸å¿
- å¯éåç»å½çé¢ UI
ç§æ·ç®¡ç
- æä¾ç§æ·ç»å½å IdentityServer4 ç§æ·ç»å½æ¹å¼
Ocelot ç½å ³(å¯é)
- éæ Ocelot å Consul
é¨ç½²
Docker æ¹å¼
HttpApi.Host
- åå¸ HttpApi.Host å°å Dockerfile å级ç®å½
-- publish -- Dockerfile
- Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:5.0
# å建ç®å½
RUN mkdir /app
COPY publish /app
# 设置工ä½ç®å½
WORKDIR /app
# æ´é²80端å£
EXPOSE 80
# 设置ç¯å¢åé
ENV ASPNETCORE_ENVIRONMENT=Production
ENTRYPOINT ["dotnet", "CompanyName.ProjectName.HttpApi.Host.dll"]
- çæ Docker éå
docker build -t abp-vnext-pro-admin .
- è¿è¡å®¹å¨
docker run -itd --name abp-vnext-pro-admin -p 8011:80 abp-vnext-pro-admin
IdentityServer.Host
- æ¥éª¤åä¸
- æå
npm run build
FROM nginx:1.17.3-alpine as base
EXPOSE 80
COPY /_nginx/nginx.conf /etc/nginx/nginx.conf
COPY /_nginx/env.js /etc/nginx/env.js
COPY /_nginx/default.conf /etc/nginx/conf.d/default.conf
COPY /dist/ /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
docker build -t abp-vnext-pro-ui .
docker run -itd --name abp-vnext-pro-ui -p 8012:80 abp-vnext-pro-ui
常è§é®é¢
VS ç¼è¯é¡¹ç®åç¬¦ä¸²è¶ è¿ 256 个å符
- æ项ç®æ·è´å°ç£çæ ¹ç®å½ OR ä½¿ç¨ Rider å¼å
Hangfire å Cap çé¢å è½½ä¸åºæ¥
- è¿ 2 个çé¢å¼å¯äºæé认è¯ï¼ç±äºå端路ç±çå¼æ¥å è½½ï¼å¯¼è´è·¯ç±å¨æ¸²æçæ¶å access_token 没æå è½½åºæ¥ï¼Ctrl+F5 å·æ°å³å¯