1. 框架整体架构解析
这个基于Vue和.NET Core的前后端分离后台管理系统框架,采用了经典的B/S架构设计。前端基于Vue 2.6 + ElementUI构建用户界面,后端使用.NET Core 3.1提供API服务,两者通过RESTful API进行数据交互。
技术栈选型考量:
- 前端选择Vue 2.6而非Vue 3的主要考虑是企业项目对稳定性的要求高于新特性
- ElementUI作为UI组件库,提供了丰富的后台管理系统常用组件
- .NET Core 3.1是LTS版本,保证了长期支持且性能优异
- Dapper作为ORM工具,在性能和灵活性之间取得了良好平衡
提示:虽然Vue 2已停止维护,但对于已有项目或团队技术栈固定的场景,Vue 2仍然是合理选择。框架作者表示后续会提供Vue 3迁移指南。
2. 前端核心功能实现
2.1 动态路由与权限控制
路由配置采用前端控制模式,权限信息存储在路由meta字段中。系统初始化时,会根据用户权限动态过滤可访问路由。
javascript复制// 更完整的路由配置示例
const routes = [
{
path: '/dashboard',
component: Layout,
redirect: '/dashboard/index',
meta: { title: '控制台', icon: 'dashboard', affix: true },
children: [{
path: 'index',
component: () => import('@/views/dashboard/index'),
meta: { title: '首页', keepAlive: true }
}]
},
{
path: '/system',
component: Layout,
meta: { title: '系统管理', icon: 'system', permission: ['admin'] },
children: [
{
path: 'user',
component: () => import('@/views/system/user'),
meta: { title: '用户管理', permission: ['user:view'] }
},
{
path: 'role',
component: () => import('@/views/system/role'),
meta: { title: '角色管理', permission: ['role:view'] }
}
]
}
]
权限控制实现要点:
- 路由分为基础路由和动态路由两部分
- 用户登录后获取权限列表,过滤动态路由
- 使用addRoutes动态注入过滤后的路由
- 按钮级权限通过v-permission指令控制
2.2 状态管理与API封装
框架使用Vuex进行状态管理,axios进行HTTP请求。特别对axios进行了深度封装:
javascript复制// axios封装示例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 10000
})
// 请求拦截器
service.interceptors.request.use(config => {
config.headers['X-Token'] = getToken()
config.headers['X-Tenant-Id'] = store.getters.tenantId
return config
})
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
if (res.code === 401) {
// 跳转登录
}
return Promise.reject(new Error(res.message || 'Error'))
}
return res
},
error => {
return Promise.reject(error)
}
)
3. 后端核心功能实现
3.1 多租户架构设计
框架支持共享数据库独立Schema和独立数据库两种多租户模式。通过中间件识别租户,自动进行数据隔离。
csharp复制// 更完整的租户中间件
public class TenantMiddleware
{
private readonly RequestDelegate _next;
private readonly TenantSettings _tenantSettings;
public TenantMiddleware(RequestDelegate next, IOptions<TenantSettings> options)
{
_next = next;
_tenantSettings = options.Value;
}
public async Task InvokeAsync(HttpContext context, ITenantService tenantService)
{
// 从header、cookie或subdomain获取租户标识
var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault()
?? context.Request.Cookies["tenant_id"]?.ToString()
?? context.Request.Host.Host.Split('.')[0];
if (!string.IsNullOrEmpty(tenantId))
{
var tenant = tenantService.GetTenant(tenantId);
if (tenant != null)
{
context.Items["CurrentTenant"] = tenant;
// 设置租户特定配置
if (tenant.ConnectionString != null)
{
_tenantSettings.DefaultConnectionString = tenant.ConnectionString;
}
}
}
await _next(context);
}
}
3.2 多数据库支持实现
框架通过抽象数据访问层,支持多种数据库无缝切换。核心是DBProvider工厂模式:
csharp复制public static class DbProviderFactory
{
public static IDbConnection CreateConnection(IConfiguration config)
{
var dbType = config["DbType"] ?? "SqlServer";
var connectionString = config.GetConnectionString("Default");
return dbType.ToLower() switch
{
"mysql" => new MySqlConnection(connectionString),
"oracle" => new OracleConnection(connectionString),
"postgresql" => new NpgsqlConnection(connectionString),
_ => new SqlConnection(connectionString)
};
}
public static IServiceCollection AddDatabase(this IServiceCollection services, IConfiguration config)
{
var dbType = config["DbType"] ?? "SqlServer";
services.AddScoped<IDbConnection>(_ => CreateConnection(config));
switch (dbType.ToLower())
{
case "mysql":
services.AddTransient<ISqlGenerator, MySqlGenerator>();
break;
case "oracle":
services.AddTransient<ISqlGenerator, OracleSqlGenerator>();
break;
default:
services.AddTransient<ISqlGenerator, SqlServerGenerator>();
break;
}
return services;
}
}
4. 开发效率提升工具
4.1 代码生成器实现
框架内置了强大的代码生成器,可以根据数据库表结构自动生成前后端代码:
csharp复制// 代码生成器核心逻辑示例
public class CodeGenerator
{
public void Generate(TableSchema table, CodeGenOptions options)
{
// 1. 生成实体类
GenerateEntity(table, options);
// 2. 生成Service层
GenerateService(table, options);
// 3. 生成Controller
GenerateController(table, options);
// 4. 生成前端Vue组件
GenerateVueComponent(table, options);
}
private void GenerateEntity(TableSchema table, CodeGenOptions options)
{
var template = ReadTemplate("EntityTemplate.cs");
var content = template.Replace("{{ClassName}}", table.ClassName)
.Replace("{{Properties}}", GenerateProperties(table));
WriteFile($"{options.OutputDir}/Entities/{table.ClassName}.cs", content);
}
// 其他生成方法...
}
4.2 审计日志实现
框架通过拦截器自动记录数据变更日志:
csharp复制public class AuditInterceptor : DbCommandInterceptor
{
public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
if (command.CommandText.StartsWith("UPDATE") ||
command.CommandText.StartsWith("INSERT") ||
command.CommandText.StartsWith("DELETE"))
{
var auditLog = new AuditLog
{
TableName = GetTableName(command),
Action = GetAction(command),
OldValues = GetOldValues(command),
NewValues = GetNewValues(command),
UserId = GetCurrentUserId(),
Timestamp = DateTime.UtcNow
};
SaveAuditLog(auditLog);
}
return base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
}
}
5. 部署与性能优化
5.1 前端部署优化
- 使用Webpack进行代码分割和懒加载
- 配置Gzip压缩减少资源体积
- 启用HTTP/2提升加载速度
- 使用CDN加速静态资源
javascript复制// vue.config.js 优化配置
module.exports = {
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial'
},
elementUI: {
name: 'chunk-elementUI',
priority: 20,
test: /[\\/]node_modules[\\/]_?element-ui(.*)/
}
}
}
}
},
chainWebpack: config => {
config.plugin('compression').use(CompressionPlugin, [{
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240
}])
}
}
5.2 后端性能优化
- 使用缓存减少数据库访问
- 实现异步编程模式
- 启用响应压缩
- 优化EF Core查询
csharp复制// 缓存实现示例
public class CachedRepository<T> : IRepository<T> where T : class
{
private readonly IRepository<T> _decorated;
private readonly IDistributedCache _cache;
public CachedRepository(IRepository<T> decorated, IDistributedCache cache)
{
_decorated = decorated;
_cache = cache;
}
public async Task<T> GetByIdAsync(int id)
{
string cacheKey = $"{typeof(T).Name}_{id}";
var cachedItem = await _cache.GetStringAsync(cacheKey);
if (cachedItem != null)
{
return JsonSerializer.Deserialize<T>(cachedItem);
}
var item = await _decorated.GetByIdAsync(id);
if (item != null)
{
await _cache.SetStringAsync(cacheKey,
JsonSerializer.Serialize(item),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
});
}
return item;
}
}
6. 常见问题与解决方案
6.1 跨域问题处理
虽然开发环境配置了CORS,但生产环境可能需要更严格的设置:
csharp复制// Startup.cs中配置CORS
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("DefaultPolicy", builder =>
{
builder.WithOrigins("https://example.com")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
}
public void Configure(IApplicationBuilder app)
{
app.UseCors("DefaultPolicy");
}
6.2 权限缓存问题
权限变更后需要及时清除缓存:
javascript复制// 前端权限缓存处理
const permission = {
state: {
permissions: []
},
mutations: {
SET_PERMISSIONS: (state, permissions) => {
state.permissions = permissions
localStorage.setItem('permissions', JSON.stringify(permissions))
},
CLEAR_PERMISSIONS: (state) => {
state.permissions = []
localStorage.removeItem('permissions')
}
},
actions: {
async getPermissions({ commit }) {
try {
const cached = localStorage.getItem('permissions')
if (cached) {
commit('SET_PERMISSIONS', JSON.parse(cached))
return
}
const { data } = await getPermissions()
commit('SET_PERMISSIONS', data)
} catch (error) {
commit('CLEAR_PERMISSIONS')
throw error
}
}
}
}
6.3 多租户数据隔离问题
确保所有查询都自动添加租户过滤条件:
csharp复制// 仓储基类实现租户过滤
public class Repository<T> : IRepository<T> where T : class, ITenantEntity
{
private readonly DbContext _context;
private readonly Tenant _currentTenant;
public Repository(DbContext context, ICurrentTenant currentTenant)
{
_context = context;
_currentTenant = currentTenant.Get();
}
public IQueryable<T> Queryable()
{
return _context.Set<T>().Where(x => x.TenantId == _currentTenant.Id);
}
public async Task AddAsync(T entity)
{
if (entity is ITenantEntity tenantEntity)
{
tenantEntity.TenantId = _currentTenant.Id;
}
await _context.Set<T>().AddAsync(entity);
}
}
7. 框架扩展与定制
7.1 插件机制实现
框架支持通过插件机制扩展功能:
csharp复制// 插件接口定义
public interface IPlugin
{
string Name { get; }
string Version { get; }
void Initialize(IServiceCollection services, IConfiguration configuration);
void Configure(IApplicationBuilder app);
}
// 插件管理器实现
public class PluginManager
{
private readonly List<IPlugin> _plugins = new List<IPlugin>();
public void LoadPlugins(string pluginsPath)
{
foreach (var dll in Directory.GetFiles(pluginsPath, "*.dll"))
{
var assembly = Assembly.LoadFrom(dll);
var pluginTypes = assembly.GetTypes()
.Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface);
foreach (var type in pluginTypes)
{
var plugin = (IPlugin)Activator.CreateInstance(type);
_plugins.Add(plugin);
}
}
}
public void InitializePlugins(IServiceCollection services, IConfiguration config)
{
foreach (var plugin in _plugins)
{
plugin.Initialize(services, config);
}
}
public void ConfigurePlugins(IApplicationBuilder app)
{
foreach (var plugin in _plugins)
{
plugin.Configure(app);
}
}
}
7.2 主题定制方案
前端支持通过SCSS变量轻松定制主题:
scss复制// variables.scss
$--color-primary: #409EFF;
$--color-success: #67C23A;
$--color-warning: #E6A23C;
$--color-danger: #F56C6C;
$--color-info: #909399;
// 覆盖ElementUI默认变量
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': $--color-primary,
),
'success': (
'base': $--color-success,
),
'warning': (
'base': $--color-warning,
),
'danger': (
'base': $--color-danger,
),
'info': (
'base': $--color-info,
),
)
);
// 在vue.config.js中配置
module.exports = {
css: {
loaderOptions: {
sass: {
additionalData: `@import "@/styles/variables.scss";`
}
}
}
}
8. 安全加固措施
8.1 接口安全防护
- 防SQL注入
- 防XSS攻击
- 防CSRF攻击
- 请求频率限制
csharp复制// 安全中间件示例
public class SecurityMiddleware
{
private readonly RequestDelegate _next;
private readonly IMemoryCache _cache;
public SecurityMiddleware(RequestDelegate next, IMemoryCache cache)
{
_next = next;
_cache = cache;
}
public async Task InvokeAsync(HttpContext context)
{
// 1. 检查SQL注入关键词
if (ContainsSqlInjection(context.Request))
{
context.Response.StatusCode = 400;
return;
}
// 2. 检查XSS攻击
if (ContainsXss(context.Request))
{
context.Response.StatusCode = 400;
return;
}
// 3. CSRF防护
if (!context.Request.Path.StartsWithSegments("/api") ||
context.Request.Method == "GET")
{
await _next(context);
return;
}
if (!ValidateAntiForgeryToken(context))
{
context.Response.StatusCode = 403;
return;
}
// 4. 请求频率限制
var clientIp = context.Connection.RemoteIpAddress.ToString();
var cacheKey = $"rate_limit_{clientIp}";
if (_cache.TryGetValue(cacheKey, out int count) && count > 100)
{
context.Response.StatusCode = 429;
return;
}
_cache.Set(cacheKey, count + 1, TimeSpan.FromMinutes(1));
await _next(context);
}
}
8.2 数据加密方案
- 敏感数据加密存储
- 传输数据HTTPS加密
- JWT令牌安全
csharp复制// 数据加密服务示例
public class CryptoService
{
private readonly byte[] _key;
private readonly byte[] _iv;
public CryptoService(IConfiguration config)
{
_key = Convert.FromBase64String(config["Crypto:Key"]);
_iv = Convert.FromBase64String(config["Crypto:IV"]);
}
public string Encrypt(string plainText)
{
using var aes = Aes.Create();
aes.Key = _key;
aes.IV = _iv;
var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using var ms = new MemoryStream();
using var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);
using (var sw = new StreamWriter(cs))
{
sw.Write(plainText);
}
return Convert.ToBase64String(ms.ToArray());
}
public string Decrypt(string cipherText)
{
using var aes = Aes.Create();
aes.Key = _key;
aes.IV = _iv;
var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using var ms = new MemoryStream(Convert.FromBase64String(cipherText));
using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
using var sr = new StreamReader(cs);
return sr.ReadToEnd();
}
}
9. 监控与日志系统
9.1 操作日志记录
框架内置了完善的操作日志记录功能:
csharp复制// 操作日志过滤器
public class OperationLogFilter : IAsyncActionFilter
{
private readonly ILogger<OperationLogFilter> _logger;
public OperationLogFilter(ILogger<OperationLogFilter> logger)
{
_logger = logger;
}
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
var sw = Stopwatch.StartNew();
var result = await next();
sw.Stop();
var log = new OperationLog
{
UserId = GetCurrentUserId(),
UserName = GetCurrentUserName(),
Url = context.HttpContext.Request.Path,
Method = context.HttpContext.Request.Method,
Parameters = JsonSerializer.Serialize(context.ActionArguments),
StatusCode = context.HttpContext.Response.StatusCode,
ExecutionTime = sw.ElapsedMilliseconds,
ClientIP = context.HttpContext.Connection.RemoteIpAddress?.ToString(),
Browser = context.HttpContext.Request.Headers["User-Agent"].ToString(),
OperationTime = DateTime.Now
};
_logger.LogInformation("OperationLog: {@Log}", log);
}
}
9.2 性能监控实现
使用MiniProfiler进行性能监控:
csharp复制// Startup.cs中配置
public void ConfigureServices(IServiceCollection services)
{
services.AddMiniProfiler(options =>
{
options.RouteBasePath = "/profiler";
options.ColorScheme = StackExchange.Profiling.ColorScheme.Auto;
options.Storage = new MemoryCacheStorage(TimeSpan.FromMinutes(60));
options.SqlFormatter = new StackExchange.Profiling.SqlFormatters.InlineFormatter();
options.TrackConnectionOpenClose = true;
}).AddEntityFramework();
}
public void Configure(IApplicationBuilder app)
{
app.UseMiniProfiler();
// 其他中间件...
}
10. 测试策略与实践
10.1 单元测试实现
后端使用xUnit进行单元测试:
csharp复制// 服务层单元测试示例
public class UserServiceTests : IDisposable
{
private readonly ApplicationDbContext _context;
private readonly UserService _userService;
public UserServiceTests()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
_context = new ApplicationDbContext(options);
_userService = new UserService(_context, Mock.Of<ILogger<UserService>>());
// 初始化测试数据
_context.Users.Add(new User { Id = 1, UserName = "test1", Password = "123456" });
_context.SaveChanges();
}
[Fact]
public async Task GetUserById_ShouldReturnUser_WhenUserExists()
{
// Arrange
var userId = 1;
// Act
var user = await _userService.GetUserById(userId);
// Assert
Assert.NotNull(user);
Assert.Equal(userId, user.Id);
}
[Fact]
public async Task GetUserById_ShouldReturnNull_WhenUserNotExists()
{
// Arrange
var userId = 999;
// Act
var user = await _userService.GetUserById(userId);
// Assert
Assert.Null(user);
}
public void Dispose()
{
_context.Dispose();
}
}
10.2 前端组件测试
使用Jest测试Vue组件:
javascript复制// UserList.spec.js
import { shallowMount } from '@vue/test-utils'
import UserList from '@/components/UserList.vue'
describe('UserList.vue', () => {
it('renders user list correctly', () => {
const users = [
{ id: 1, name: 'User 1' },
{ id: 2, name: 'User 2' }
]
const wrapper = shallowMount(UserList, {
propsData: { users }
})
expect(wrapper.findAll('li').length).toBe(users.length)
})
it('emits select event when user is clicked', async () => {
const users = [{ id: 1, name: 'User 1' }]
const wrapper = shallowMount(UserList, {
propsData: { users }
})
await wrapper.find('li').trigger('click')
expect(wrapper.emitted().select).toBeTruthy()
expect(wrapper.emitted().select[0]).toEqual([users[0].id])
})
})
11. 持续集成与部署
11.1 CI/CD流水线配置
使用GitHub Actions实现自动化构建部署:
yaml复制# .github/workflows/build.yml
name: Build and Deploy
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# 构建前端
- name: Build Frontend
run: |
cd frontend
npm install
npm run build
env:
CI: true
# 构建后端
- name: Build Backend
run: |
cd backend
dotnet restore
dotnet build --configuration Release --no-restore
# 运行测试
- name: Run Tests
run: |
cd backend
dotnet test --no-build --configuration Release
# 部署到测试环境
- name: Deploy to Staging
if: github.ref == 'refs/heads/main'
run: |
scp -r frontend/dist user@staging:/var/www/admin
scp backend/bin/Release/netcoreapp3.1/publish/* user@staging:/app/admin
11.2 Docker容器化部署
框架支持通过Docker快速部署:
dockerfile复制# 前端Dockerfile
FROM node:14 as build-stage
WORKDIR /app
COPY frontend/package*.json ./
RUN npm install
COPY frontend .
RUN npm run build
FROM nginx:alpine
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
dockerfile复制# 后端Dockerfile
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src
COPY backend/*.csproj .
RUN dotnet restore
COPY backend .
RUN dotnet publish -c Release -o /app
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "Admin.WebApi.dll"]
12. 项目迁移与升级
12.1 数据库迁移方案
使用EF Core迁移命令管理数据库变更:
bash复制# 创建迁移
dotnet ef migrations add InitialCreate --project Admin.Infrastructure --startup-project Admin.WebApi
# 应用迁移
dotnet ef database update --project Admin.Infrastructure --startup-project Admin.WebApi
# 生成SQL脚本
dotnet ef migrations script --project Admin.Infrastructure --startup-project Admin.WebApi -o migration.sql
12.2 Vue 2到Vue 3升级路径
虽然当前框架基于Vue 2,但可以按以下步骤升级到Vue 3:
- 逐步替换ElementUI为Element Plus
- 使用@vue/compat构建兼容版本
- 分模块迁移组件
- 更新Vue Router和Vuex到最新版
javascript复制// 兼容性配置示例
module.exports = {
configureWebpack: {
resolve: {
alias: {
vue$: '@vue/compat'
}
}
},
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
return {
...options,
compilerOptions: {
compatConfig: {
MODE: 2 // 启用所有Vue 2兼容特性
}
}
}
})
}
}
13. 项目结构与代码规范
13.1 前端目录结构
code复制frontend
├── public # 静态资源
├── src
│ ├── api # API请求
│ ├── assets # 静态资源
│ ├── components # 公共组件
│ ├── directives # 自定义指令
│ ├── filters # 过滤器
│ ├── icons # SVG图标
│ ├── layout # 布局组件
│ ├── router # 路由配置
│ ├── store # Vuex状态管理
│ ├── styles # 全局样式
│ ├── utils # 工具函数
│ ├── views # 页面组件
│ ├── App.vue # 根组件
│ └── main.js # 入口文件
├── tests # 测试代码
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
└── vue.config.js # Vue CLI配置
13.2 后端项目结构
code复制backend
├── Admin.Application # 应用服务层
├── Admin.Domain # 领域模型
├── Admin.Infrastructure # 基础设施层
├── Admin.WebApi # Web API层
├── tests # 测试项目
├── migrations # 数据库迁移
└── Dockerfile # Docker配置
14. 性能调优实战
14.1 前端性能优化
- 按需加载ElementUI组件
- 使用Tree Shaking移除未使用代码
- 配置长期缓存
- 图片懒加载
javascript复制// 按需加载ElementUI
import { ElButton, ElInput } from 'element-plus'
export default {
components: {
ElButton,
ElInput
}
}
// 图片懒加载指令
Vue.directive('lazy', {
inserted: (el, binding) => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
el.src = binding.value
observer.unobserve(el)
}
})
})
observer.observe(el)
}
})
14.2 后端性能优化
- 使用缓存减少数据库查询
- 实现异步编程模式
- 优化EF Core查询
- 启用响应压缩
csharp复制// 查询优化示例
public async Task<List<UserDto>> GetUsersAsync(UserQuery query)
{
var q = _context.Users.AsNoTracking();
if (!string.IsNullOrEmpty(query.Name))
{
q = q.Where(u => u.Name.Contains(query.Name));
}
if (query.RoleId.HasValue)
{
q = q.Where(u => u.RoleId == query.RoleId.Value);
}
return await q.OrderBy(u => u.Name)
.Skip((query.Page - 1) * query.Size)
.Take(query.Size)
.Select(u => new UserDto
{
Id = u.Id,
Name = u.Name,
Email = u.Email,
RoleName = u.Role.Name
})
.ToListAsync();
}
15. 项目文档与协作
15.1 API文档生成
使用Swagger生成API文档:
csharp复制// Startup.cs配置Swagger
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Admin API", Version = "v1" });
// 添加JWT认证
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
}
public void Configure(IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Admin API V1");
});
}
15.2 项目文档编写
使用Vitepress编写项目文档:
markdown复制# 项目文档
## 快速开始
### 环境要求
- Node.js 14+
- .NET Core SDK 3.1
- MySQL 5.7+/SQL Server 2012+
### 安装步骤
1. 克隆仓库
```bash
git clone https://github.com/your-repo/admin-framework.git
- 安装前端依赖
bash复制cd frontend
npm install
- 运行后端
bash复制cd backend
dotnet run
开发指南
添加新页面
- 在
src/views下创建Vue组件 - 在
src/router/routes.js中添加路由配置 - 在
src/api中添加对应的API调用
code复制
## 16. 国际化实现方案
### 16.1 前端国际化
使用vue-i18n实现多语言支持:
```javascript
// i18n配置
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import en from './locales/en.json'
import zh from './locales/zh.json'
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: localStorage.getItem('locale') || 'zh',
messages: { en, zh }
})
// 语言切换组件
<template>
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link">
{{ $t('language') }}<i class="el-icon-arrow-down"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="zh">中文</el-dropdown-item>
<el-dropdown-item command="en">English</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
methods: {
handleCommand(lang) {
this.$i18n.locale = lang
localStorage.setItem('locale', lang)
}
}
}
</script>
16.2 后端国际化
使用资源文件实现多语言API响应:
csharp复制// 本地化服务
public class LocalizationService : ILocalizationService
{
private readonly IStringLocalizer _localizer;
public LocalizationService(IStringLocalizerFactory factory)
{
_localizer = factory.Create(typeof(SharedResource));
}
public string this[string key] => _localizer[key];
public string Get(string key, params object[] args)
{
return _localizer[key, args];
}
}
// 在控制器中使用
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly ILocalizationService _localizer;
public UsersController(ILocalizationService localizer)
{
_localizer = localizer;
}
[HttpGet]
public IActionResult Get()
{
return Ok(new {
Message = _localizer["WelcomeMessage"]
});
}
}
17. 异常处理与日志
17.1 全局异常处理
csharp复制// 自定义异常中间件
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Global exception caught");
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
var statusCode = exception switch
{
ValidationException => StatusCodes.Status400BadRequest,
UnauthorizedAccessException => StatusCodes.Status401Unauthorized,
_ => StatusCodes.Status500InternalServerError
};
context.Response.StatusCode = statusCode;
return context.Response.WriteAsync(JsonSerializer.Serialize(new
{
Status = statusCode,
Message = exception.Message,
Detail = exception.InnerException?.Message
}));
}
}
17.2 结构化日志
使用Serilog