diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..9d58272 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "8.0.2", + "commands": [ + "dotnet-ef" + ] + } + } +} \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1dca08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +obj +bin +.vs +logs +upload +data1 diff --git a/Appsettings.cs b/Appsettings.cs new file mode 100644 index 0000000..c61c438 --- /dev/null +++ b/Appsettings.cs @@ -0,0 +1,77 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace dy.net +{ + /// + /// appsettings.json操作类 + /// + public class Appsettings + { + public static IConfiguration Configuration { get; private set; } + + public Appsettings(string contentPath) + { + //如果你把配置文件 是 根据环境变量来分开了,可以这样写 + string Path = $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json"; + + Configuration = new ConfigurationBuilder() + .SetBasePath(contentPath) + .Add(new JsonConfigurationSource { Path = Path, Optional = false, ReloadOnChange = true })//这样的话,可以直接读目录里的json文件,而不是 bin 文件夹下的,所以不用修改复制属性 + .Build(); + } + + public Appsettings(IConfiguration configuration) + { + Configuration = configuration; + } + + /// + /// 封装要操作的字符 + /// + /// 节点配置 + /// + public static string Get(params string[] sections) + { + try + { + + if (sections.Any()) + { + return Configuration[string.Join(":", sections)]; + } + } + catch (Exception) { throw; } + + return ""; + } + + /// + /// 递归获取配置信息数组 + /// + /// + /// + /// + public static List Get(params string[] sections) + { + List list = new List(); + // 引用 Microsoft.Extensions.Configuration.Binder 包 + Configuration.Bind(string.Join(":", sections), list); + return list; + } + + + public static T Get(string key) where T : new() + { + return Configuration.GetSection(key).Get(); + + } + public static string Get(string Key) + { + return Configuration[Key]; + } + } +} diff --git a/Controllers/AuthController.cs b/Controllers/AuthController.cs new file mode 100644 index 0000000..c79a22c --- /dev/null +++ b/Controllers/AuthController.cs @@ -0,0 +1,164 @@ +using ClockSnowFlake; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using dy.net.service; +using dy.net.utils; +using dy.net.dto; + +namespace dy.net.Controllers +{ + [Route("api/[controller]/[action]")] + [ApiController] + public class AuthController : ControllerBase + { + private readonly IWebHostEnvironment webHostEnvironment; + + private readonly UserService _userService; + public AuthController(UserService userService, IWebHostEnvironment webHostEnvironment ) + { + _userService=userService; + this.webHostEnvironment = webHostEnvironment; + } + + + /// + /// 修改密码 + /// + /// + /// + [Authorize] + [HttpPost] + public async Task UpdatePwd(UpdatePwdRequest user) + { + var (code, erro) = await _userService.UpdatePwd(user); + return Ok(new { code, erro }); + } + + + + [Authorize] + [HttpGet] + public async Task GetUserAvatar() + { + var user = await _userService.GetUser(); + return Ok(new { code = 0, error = "", data = new { user?.Avatar, user?.Id,user?.UserName } }); + } + + /// + /// 修改用户头像 + /// + /// + /// + [Authorize] + [HttpPost] + public async Task UpdateUserAvatar(IFormFile file) + { + if (file != null && file.Length > 0) + { + long maxFileSize = 5 * 1024 * 1024; // 限制文件大小为5MB + if (file.Length > maxFileSize) + { + return Ok(new { code = -1, erro = "文件最大只能上传5M" }); + } + var fileName = $"{IdGener.GetGuid()}_{file.FileName}"; + var filePath = webHostEnvironment.IsProduction() ? + Path.Combine(Md5Util.UPLOAD_PATH_PRO, fileName) : Path.Combine(Md5Util.UPLOAD_PATH_DEV, fileName); + using (var stream = new FileStream(filePath, FileMode.Create)) + { + await file.CopyToAsync(stream); + + try + { + // 问了节约空间,删除文件夹下的所有文件 + string[] files = Directory.GetFiles(webHostEnvironment.IsProduction() ? Md5Util.UPLOAD_PATH_PRO : Md5Util.UPLOAD_PATH_DEV); + foreach (string mfile in files) + { + if (!mfile.Contains(fileName)) + System.IO.File.Delete(mfile); + } + } + catch (Exception ex) + { + Serilog.Log.Error($"delete file error ,{ex.Message}"); + } + var update = await _userService.UpdateAvatar( fileName); + + return Ok(new { code = update ? 0 : -1, erro = update ? "" : "上传失败", data = update ? fileName : "" }); + } + } + else + { + return Ok(new { code = -1, erro = "空文件" }); + } + } + + + + /// + /// 登录获取token + /// + /// + /// + [HttpPost] + public async Task Login(LoginRequest loginUserInfo) + { + if (loginUserInfo == null) + { + return Ok(new { code = -1, erro = "参数不能为空" }); + } + else + { + var user = await _userService.GetUser(); + + if (user == null) + { + return Ok(new { code = -1, erro = "用户名或密码不正确" }); + } + else + { + if (user.Password == Md5Util.Md5(loginUserInfo.Password)) + { + + var tokenString = GenerateJwtToken(user.UserName); + + return Ok(new { code = 0, erro = "", token = tokenString, expires = 24 * 60 * 60 * 1000 }); + } + else + { + return Ok(new { code = -1, erro = "用户名或密码不正确" }); + } + } + } + } + + + private string GenerateJwtToken(string username) + { + var claims = new[] + { + new Claim(ClaimTypes.Name, username), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + }; + var k = Md5Util.JWT_TOKEN_KEY; + var key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(k)); + var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var expires = DateTime.Now.AddDays(1); + + var token = new JwtSecurityToken( + issuer: IdGener.GetLong().ToString(), + audience: IdGener.GetLong().ToString(), + claims: claims, + expires: expires, + signingCredentials: credentials + ); + + var jwtToken = new JwtSecurityTokenHandler().WriteToken(token); + return jwtToken; + } + } +} diff --git a/Controllers/ConfigController.cs b/Controllers/ConfigController.cs new file mode 100644 index 0000000..6644386 --- /dev/null +++ b/Controllers/ConfigController.cs @@ -0,0 +1,147 @@ +using ClockSnowFlake; +using dy.net.dto; +using dy.net.model; +using dy.net.service; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using static Dm.net.buffer.ByteArrayBuffer; + +namespace dy.net.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ConfigController : ControllerBase + { + private readonly DyCookieService dyCookieService; + + private readonly CommonService commonService; + private readonly QuartzJobService quartzJobService; + + public ConfigController(DyCookieService dyCookieService, CommonService commonService,QuartzJobService quartzJobService) + { + this.dyCookieService = dyCookieService; + this.commonService = commonService; + this.quartzJobService = quartzJobService; + } + + + + + /// + /// 分页查询 + /// + /// 分页结果(视频列表和总数) + [HttpPost("paged")] + public async Task GetPagedAsync( + PageRequestDto dto) + { + var (list, totalCount) = await dyCookieService.GetPagedAsync(dto.PageIndex, dto.PageSize); + return Ok(new + { + code = 0, + data = new + { + data = list, + total=totalCount, + pageIndex= dto.PageIndex, + pageSize= dto.PageSize + } + } + ); + } + /// + /// 新增用户Cookie + /// + [HttpPost("add")] + public async Task AddAsync([FromBody] DyUserCookies dyUserCookies) + { + var result = await dyCookieService.Add(dyUserCookies); + if (result) + { + await ReStartJob(); + return Ok(new { code = 0 }); + } + return BadRequest(new { code=-1, message = "添加失败" }); + } + + /// + /// 更新用户Cookie + /// + [HttpPost("update")] + public async Task UpdateAsync([FromBody] DyUserCookies dyUserCookies) + { + if (dyUserCookies.Id == "0") + { + dyUserCookies.Id=IdGener.GetLong().ToString(); + var result = await dyCookieService.Add(dyUserCookies); + if (result) + { + await ReStartJob(); + return Ok(new { code = 0 }); + } + return BadRequest(new { code = -1, message = "添加失败" }); + } + else { + var result = await dyCookieService.UpdateAsync(dyUserCookies); + if (result) + { + await ReStartJob(); + return Ok(new { code = 0 }); + } + return BadRequest(new { code = -1, message = "更新失败" }); + } + + } + + /// + /// 批量删除用户Cookie + /// + [HttpGet("delete")] + public async Task DeleteAsync(string id) + { + var count = await dyCookieService.DeleteByIdsAsync(new List { id}); + if (count > 0) { + await ReStartJob(); + } + return Ok(new { code = 0, deletedCount = count }); + } + + [HttpGet("GetConfig")] + public IActionResult GetConfig() + { + var data = commonService.GetConfig(); + return Ok(new { code = 0, data = data }); + } + + [HttpPost("UpdateConfig")] + public async Task UpdateConfig(AppConfig config) + { + var data = await commonService.UpdateConfig(config); + if (data) { + await ReStartJob(); + } + return Ok(new { code = 0, data = data }); + } + + /// + /// + /// + /// + [HttpGet("ExecuteJobNow")] + [Authorize] + public async Task ExecuteJobNow() + { + var config = commonService.GetConfig(); + await quartzJobService.StartJob(config.Cron); + return Ok(new { code = 0 , error = "" }); + } + + + private async Task ReStartJob() { + var config= commonService.GetConfig(); + if(config!=null) + await quartzJobService.StartJob(config.Cron); + } + } +} diff --git a/Controllers/LogsController.cs b/Controllers/LogsController.cs new file mode 100644 index 0000000..3bd2145 --- /dev/null +++ b/Controllers/LogsController.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Net.Http; + +namespace dy.net.Controllers +{ + [Route("api/[controller]/[action]")] + [ApiController] + public class LogsController : ControllerBase + { + private readonly IWebHostEnvironment webHostEnvironment; + + public LogsController(IWebHostEnvironment webHostEnvironment) + { + this.webHostEnvironment = webHostEnvironment; + } + + [HttpGet] + public async Task GetLog(string type, string date) + { + var filePath = Path.Combine(webHostEnvironment.IsDevelopment() ? Directory.GetCurrentDirectory() : AppDomain.CurrentDomain.BaseDirectory, "logs", $"log-{type}-{date}.txt"); + if (!System.IO.File.Exists(filePath)) + { + var msg = $"Log file log-{type}-{date}.txt not found."; + //Serilog.Log.Error(msg); + return Ok(msg); + } + return PhysicalFile(filePath, "text/plain; charset=utf-8"); + + //下面的方案提示文件被占用 + //var encoding = Encoding.GetEncoding("UTF-8"); // 指定文本文件的编码 + //var fileBytes = await System.IO.File.ReadAllBytesAsync(filePath); + //var fileContent = encoding.GetString(fileBytes); + //return Content (fileContent, "text/plain", encoding); + } + + + } +} diff --git a/Controllers/VideoController.cs b/Controllers/VideoController.cs new file mode 100644 index 0000000..8f6548e --- /dev/null +++ b/Controllers/VideoController.cs @@ -0,0 +1,56 @@ +using dy.net.dto; +using dy.net.service; +using Microsoft.AspNetCore.Mvc; +using System.Drawing.Printing; + +namespace dy.net.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class VideoController : ControllerBase + { + private readonly DyCollectVideoService dyCollectVideoService; + + public VideoController(DyCollectVideoService dyCollectVideoService) + { + this.dyCollectVideoService = dyCollectVideoService; + } + /// + /// 分页查询收藏视频 + /// + /// + [HttpPost("paged")] + public async Task GetPagedAsync(VideoPageRequestDTO dto) + { + var (list, totalCount) = await dyCollectVideoService.GetPagedAsync(dto.PageIndex, dto.PageSize, dto.Tag, dto.Author,dto.ViedoType,dto.Dates); + return Ok(new + { + code = 0, + data = new + { + data = list, + total=totalCount, + pageIndex=dto.PageIndex, + pageSize=dto.PageSize + } + }); + } + + /// + /// 查询统计数据 + /// + /// + [HttpGet("statics")] + public async Task GetStaticsAsync() + { + var data = await dyCollectVideoService.GetStatics(); + return Ok(new + { + code = 0, + data + }); + } + + + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6fa7ada --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 10101 + +COPY . . +ENV ASPNETCORE_URLS http://*:10101 +ENTRYPOINT ["dotnet", "dy.net.dll"] +ENV TZ=Asia/Shanghai \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..731ddd3 --- /dev/null +++ b/Program.cs @@ -0,0 +1,326 @@ +using ClockSnowFlake; +using dy.net.dto; +using dy.net.extension; +using dy.net.model; +using dy.net.service; +using dy.net.utils; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.FileProviders; +using Microsoft.IdentityModel.Tokens; +using Serilog; +using SqlSugar; +using System.Reflection; +using System.Text; + +namespace dy.net +{ + public class Program + { + // + private const string DefaultListenUrl = "http://*:10101"; + private const string SpaRootPath = "app/dist"; + private const string SpaSourcePath = "app/"; + private const string SwaggerDocTitle = "dy.net WebApi Docs"; + + public static void Main(string[] args) + { + // ʼṩ + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + // WebӦ + var builder = WebApplication.CreateBuilder(args); + var isDevelopment = builder.Environment.IsDevelopment(); + + // + ConfigureHost(builder, isDevelopment); + + // ÷ + var services = builder.Services; + ConfigureServices(services, builder.Configuration, isDevelopment); + + // Ӧ + var app = builder.Build(); + + // м + ConfigureMiddleware(app, isDevelopment); + + // ʼӦ÷ + InitApplicationServices(app); + + // Ӧ + Serilog.Log.Debug("dy.net service started successfully"); + app.Run(); + } + + /// + /// + /// + private static void ConfigureHost(WebApplicationBuilder builder, bool isDevelopment) + { + // üַ + builder.WebHost.UseUrls(DefaultListenUrl); + + // ļ + builder.Host.ConfigureAppConfiguration((context, config) => + { + config.SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + }); + + // ־ + builder.Host.ConfigureLogging(logging => logging.ClearProviders()) + .UseSerilog(); + + builder.ConfigureLogging(); + } + + /// + /// ע + /// + private static void ConfigureServices(IServiceCollection services, IConfiguration config, bool isDevelopment) + { + // ѩID + services.AddSnowFlakeId(options => options.WorkId = new Random().Next(1, 127)); + + // MVC + services.AddControllers(); + + // HTTPͻ + services.AddHttpClients(); + + // ݿ + services.AddSqlsugar(config); + + // ʱ + services.AddQuartzService(config); + + // ִͷע + services.AddServicesFromNamespace("dy.net.repository") + .AddServicesFromNamespace("dy.net.service"); + + // SPA̬ļ֧ + services.AddSpaStaticFiles(options => options.RootPath = SpaRootPath); + + // Swagger + if (isDevelopment) + { + services.AddSwagger(); + } + + // Ӧѹ + services.AddResponseCompression(); + + // JWT֤ + ConfigureJwtAuthentication(services); + } + + /// + /// JWT֤ + /// + private static void ConfigureJwtAuthentication(IServiceCollection services) + { + var key = Encoding.ASCII.GetBytes(Md5Util.JWT_TOKEN_KEY); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + context.Token = context.Request.Headers["Authorization"] + .FirstOrDefault()?.Split(" ").Last(); + return Task.CompletedTask; + } + }; + options.RequireHttpsMetadata = false; + options.SaveToken = true; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromSeconds(60) + }; + }); + } + + /// + /// м + /// + private static void ConfigureMiddleware(WebApplication app, bool isDevelopment) + { + // Ӧѹ + app.UseResponseCompression(); + + // SwaggerUI + if (isDevelopment) + { + app.UseCustomSwaggerUI(options => options.Title = SwaggerDocTitle); + } + + // · + app.UseRouting(); + + // ֤Ȩ + app.UseAuthentication(); + app.UseAuthorization(); + + // ļϴ· + //ConfigureUploadPath(app, isDevelopment); + + // API·ӳ + app.MapControllers(); + + // SPA + if (!isDevelopment) + { + app.UseSpaStaticFiles(); + app.UseSpa(spa => spa.Options.SourcePath = SpaSourcePath); + } + } + + /// + /// ϴ· + /// + private static void ConfigureUploadPath(WebApplication app, bool isDevelopment) + { + var uploadPath = isDevelopment + ? Md5Util.UPLOAD_PATH_DEV + : Md5Util.UPLOAD_PATH_PRO; + + if (!Directory.Exists(uploadPath)) + { + Directory.CreateDirectory(uploadPath); + } + + // ļϴʿȡע + // app.UseStaticFiles(new StaticFileOptions + // { + // FileProvider = new PhysicalFileProvider(uploadPath), + // RequestPath = "/upload" + // }); + } + + /// + /// ʼӦ÷ + /// + private static void InitApplicationServices(WebApplication app) + { + using var scope = app.Services.CreateScope(); + var services = scope.ServiceProvider; + + try + { + // ʼû + var userService = services.GetRequiredService(); + userService.InitUser(new LoginUserInfo + { + UserName = "douyin", + Password = "douyin2025", + CreateTime = DateTime.Now + }); + + // ʼCookie + var cookieService = services.GetRequiredService(); + cookieService.Init(new DyUserCookies + { + UserName = "douyin", + Cookies = "--", + Id = "2026", + SavePath = "/app/collect", + Status = 0, + SecUserId = "--", + FavSavePath = "/app/favorite" + }); + + // ʼ + var commonService = services.GetRequiredService(); + var config = commonService.InitConfig(new AppConfig + { + Id = IdGener.GetLong().ToString(), + Cron = "30", + BatchCount = 10 + }); + + // ղƵ--ϰ汾-ԭľûֶ + commonService.UpdateCollectViedoType(); + + // ʱ + var quartzJobService = services.GetRequiredService(); + quartzJobService.StartJob(config?.Cron ?? "30"); + } + catch (Exception ex) + { + var logger = services.GetRequiredService>(); + logger.LogError(ex, "Failed to initialize services on startup"); + } + } + + /// + /// SQLiteݿַ + /// + private static string CreateSqliteDBConn() + { + var dbFolder = Path.Combine(Environment.CurrentDirectory, "db"); + Directory.CreateDirectory(dbFolder); // 򴴽ж + + var dbPath = Path.Combine(dbFolder, "dy.sqlite"); + if (!File.Exists(dbPath)) + { + using (File.Create(dbPath)) { } // ʹusingȷļر + } + + return $"DataSource={dbPath}"; + } + + /// + /// ʼݿ + /// + private static ISqlSugarClient InitDataBase(DbType dbType, string connString) + { + // SQLiteַ + if (dbType == DbType.Sqlite) + { + connString = CreateSqliteDBConn(); + } + + if (string.IsNullOrEmpty(connString)) + { + return null; + } + + return new SqlSugarClient(new ConnectionConfig + { + ConnectionString = connString, + InitKeyType = InitKeyType.Attribute, + DbType = dbType, + IsAutoCloseConnection = true + }, db => + { + // ־ + db.Aop.OnLogExecuting = (sql, pars) => Serilog.Log.Debug(sql); + db.Aop.OnError = e => + { + Serilog.Log.Error(e.Message); + Serilog.Log.Error(e.Sql); + }; + + // ݿͱ + db.DbMaintenance.CreateDatabase(); + var modelTypes = Assembly.GetExecutingAssembly() + .GetTypes() + .Where(t => t.Namespace?.StartsWith("dy.net.model") ?? false) + .ToArray(); + db.CodeFirst.InitTables(modelTypes); + }); + } + } +} \ No newline at end of file diff --git a/Properties/PublishProfiles/FolderProfile.pubxml b/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..3ed83c2 --- /dev/null +++ b/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,21 @@ + + + + + true + false + true + Release + Any CPU + FileSystem + bin\Release\net6.0\publish\ + FileSystem + <_TargetId>Folder + + net6.0 + 680660ef-acae-43a9-ab6c-b75532e758ad + false + + \ No newline at end of file diff --git a/Properties/PublishProfiles/FolderProfile.pubxml.user b/Properties/PublishProfiles/FolderProfile.pubxml.user new file mode 100644 index 0000000..e997050 --- /dev/null +++ b/Properties/PublishProfiles/FolderProfile.pubxml.user @@ -0,0 +1,11 @@ + + + + + <_PublishTargetUrl>F:\work\code\me\dy-sync\bin\Release\net6.0\publish\ + True|2025-09-30T05:29:42.2354235Z||;True|2025-09-25T11:44:52.6858389+08:00||;True|2025-09-25T11:08:27.8810109+08:00||;True|2025-09-25T09:28:50.2563374+08:00||;True|2025-09-24T16:14:02.3072184+08:00||;True|2025-09-23T22:38:52.4414757+08:00||;True|2025-09-23T22:19:07.1006356+08:00||;True|2025-09-23T22:18:01.5945225+08:00||;True|2025-09-23T22:06:15.6293613+08:00||;True|2025-09-23T21:53:14.2977444+08:00||;True|2025-09-23T21:46:55.2322411+08:00||;False|2025-09-23T21:45:23.7858854+08:00||;False|2025-09-23T21:03:51.8325689+08:00||;True|2025-09-23T10:03:32.2251920+08:00||;False|2025-09-23T09:38:08.2372201+08:00||;False|2025-09-23T09:37:49.9390545+08:00||;True|2024-03-11T21:31:45.8102398+08:00||;True|2024-03-11T07:26:24.7660541+08:00||;True|2024-03-08T22:08:40.0154831+08:00||;True|2024-03-03T10:14:36.8109114+08:00||;True|2024-03-02T18:44:57.3288537+08:00||;True|2024-01-24T17:51:37.9164415+08:00||;True|2024-01-24T16:36:50.5612157+08:00||;True|2024-01-24T15:51:35.7556653+08:00||;True|2024-01-17T23:40:40.7526618+08:00||;True|2024-01-17T23:36:10.3692844+08:00||;True|2024-01-17T23:22:03.2378834+08:00||;True|2024-01-03T11:35:44.7118292+08:00||;True|2024-01-03T11:11:23.4270453+08:00||;True|2024-01-03T11:04:35.2081526+08:00||;True|2024-01-03T10:57:03.7053107+08:00||;True|2024-01-03T10:51:50.7463989+08:00||;False|2024-01-03T10:50:24.9775312+08:00||;True|2024-01-03T10:47:30.1128183+08:00||;True|2024-01-03T10:42:55.8640657+08:00||;True|2024-01-03T09:24:24.3436056+08:00||;True|2024-01-02T23:40:38.2001198+08:00||;True|2024-01-02T23:08:36.7230444+08:00||;True|2024-01-02T22:53:43.9658255+08:00||;True|2024-01-02T22:25:38.5545279+08:00||;False|2024-01-02T22:24:42.6577609+08:00||;True|2024-01-02T22:04:33.4708889+08:00||;True|2024-01-02T21:49:57.9249898+08:00||;True|2023-12-28T22:42:58.5976180+08:00||;True|2023-12-28T22:24:09.2051205+08:00||;True|2023-12-28T22:05:48.4865497+08:00||;True|2023-12-28T21:50:57.4549336+08:00||;True|2023-12-28T21:45:30.4557939+08:00||;True|2023-12-28T21:19:59.3614594+08:00||;False|2023-12-28T21:17:04.3069097+08:00||;False|2023-12-28T21:15:56.7788810+08:00||;False|2023-12-28T21:14:57.3617402+08:00||;False|2023-12-28T21:10:45.4382469+08:00||;True|2023-12-28T19:45:33.1431914+08:00||;True|2023-12-26T11:30:36.6418716+08:00||;True|2023-12-26T11:17:40.6433651+08:00||;True|2023-12-26T09:13:56.0741559+08:00||;True|2023-12-26T09:10:55.5358737+08:00||;True|2023-12-25T23:15:34.6534359+08:00||;True|2023-12-25T23:09:47.1566176+08:00||;False|2023-12-25T23:07:32.1118803+08:00||;True|2023-12-25T22:59:50.4643459+08:00||;True|2023-12-25T22:50:14.3584798+08:00||;False|2023-12-25T22:49:20.0279513+08:00||;True|2023-12-25T22:44:07.4183973+08:00||;True|2023-12-25T22:35:27.3847085+08:00||;True|2023-12-25T22:23:21.3542658+08:00||;True|2023-12-25T22:12:00.5219522+08:00||;True|2023-12-25T16:52:12.5089621+08:00||;True|2023-12-25T16:43:30.8348193+08:00||;True|2023-12-25T16:35:08.2421925+08:00||;True|2023-12-25T16:25:21.3233455+08:00||;False|2023-12-25T16:24:39.1703901+08:00||;True|2023-12-25T16:24:01.4340091+08:00||;True|2023-12-25T16:10:06.5213850+08:00||;True|2023-12-25T15:42:17.1376331+08:00||;True|2023-12-25T15:29:47.8537015+08:00||;True|2023-12-25T14:04:54.3750643+08:00||;False|2023-12-25T14:04:07.9330700+08:00||;True|2023-12-25T13:51:58.1949269+08:00||;True|2023-12-25T13:41:42.4832670+08:00||;True|2023-12-25T13:00:25.3861450+08:00||;True|2023-12-25T12:48:45.3851245+08:00||;True|2023-12-25T12:46:54.1266013+08:00||;True|2023-12-25T12:37:03.2615780+08:00||;False|2023-12-25T12:36:06.2657224+08:00||;True|2023-12-25T12:31:59.6032354+08:00||;True|2023-12-25T12:26:50.2316015+08:00||;True|2023-12-25T12:18:27.4149004+08:00||;True|2023-12-25T11:59:46.1381276+08:00||;True|2023-12-25T00:36:00.0643971+08:00||;True|2023-12-25T00:28:55.0051504+08:00||;False|2023-12-25T00:21:35.4266163+08:00||;True|2023-12-25T00:18:47.0638530+08:00||;True|2023-12-25T00:12:09.6195055+08:00||;True|2023-12-25T00:01:34.9698196+08:00||;False|2023-12-24T23:50:48.6479274+08:00||;True|2023-12-22T09:54:04.1399592+08:00||;True|2023-12-22T08:59:53.2795721+08:00||;False|2023-12-18T23:40:16.8523325+08:00||; + + + \ No newline at end of file diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..a492985 --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,37 @@ +{ + "profiles": { + "dy.net": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5193" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/weatherforecast", + "publishAllPorts": true + } + }, + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:35772", + "sslPort": 0 + } + } +} \ No newline at end of file diff --git a/SecUserId.png b/SecUserId.png deleted file mode 100644 index 27d86b1..0000000 Binary files a/SecUserId.png and /dev/null differ diff --git a/app/.babelrc b/app/.babelrc new file mode 100644 index 0000000..c13c5f6 --- /dev/null +++ b/app/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015"] +} diff --git a/app/.env b/app/.env new file mode 100644 index 0000000..adce5e5 --- /dev/null +++ b/app/.env @@ -0,0 +1,2 @@ +VITE_BASE_URL=/ +VITE_API_URL=http://localhost diff --git a/app/.env.development b/app/.env.development new file mode 100644 index 0000000..c4d5c01 --- /dev/null +++ b/app/.env.development @@ -0,0 +1,2 @@ +VITE_BASE_URL=/ +VITE_API_URL=http://localhost:10101 \ No newline at end of file diff --git a/app/.env.github b/app/.env.github new file mode 100644 index 0000000..b44d27e --- /dev/null +++ b/app/.env.github @@ -0,0 +1 @@ +VITE_BASE_URL=/ \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..a85a70d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,16 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +.vscode +.history +.idea +*.log +*session.sql +package-lock.json +yarn.lock +stepin-template.session.sql +docs/.vitepress/cache +target +components.d.ts \ No newline at end of file diff --git a/app/.prettierrc.json b/app/.prettierrc.json new file mode 100644 index 0000000..8b1abb1 --- /dev/null +++ b/app/.prettierrc.json @@ -0,0 +1,22 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "endOfLine": "lf", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "singleAttributePerLine": false, + "bracketSameLine": false, + "jsxBracketSameLine": false, + "bracketLine": false, + "jsxSingleQuote": true, + "printWidth": 120, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "requirePragma": false, + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "useTabs": false, + "vueIndentScriptAndStyle": true +} diff --git a/app/LICENSE b/app/LICENSE new file mode 100644 index 0000000..1c9026b --- /dev/null +++ b/app/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 stepui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/app/README-en_US.md b/app/README-en_US.md new file mode 100644 index 0000000..f01d0b6 --- /dev/null +++ b/app/README-en_US.md @@ -0,0 +1,83 @@ +

Stepin Template

+ +
+ +开箱即用的中后台前端/设计解决方案 +
+(原[ Vue Antd Admin](https://github.com/iczer/vue-antd-admin/) Vue3 版本) + +![GitHub package.json version (subfolder of monorepo)](https://img.shields.io/github/package-json/v/stepui/stepin-template) +![GitHub repo size](https://img.shields.io/github/repo-size/stepui/stepin-template) +![GitHub last commit](https://img.shields.io/github/last-commit/stepui/stepin-template) +![GitHub release (latest by date)](https://img.shields.io/github/v/release/stepui/stepin-template) +![Docs by iczer](https://img.shields.io/badge/docs%20by-iczer-green) + +![preview](./docs/images/preview.png) + +
+ +English | [简体中文](./README.md) + +- 预览地址: https://stepui.gitee.io/stepin-template +- 使用文档: http://stepui.gitee.io/stepin-template-docs/ +- 常见问题: http://stepui.gitee.io/stepin-template-docs/issue +- 国内镜像: https://gitee.com/stepui/stepin-template + +## 特性 + +- 强大的主题定制功能,实时动态切换 +- 多页签功能,助力后台管理高效开发 +- 内置权限控制,兼容 ABAC 和 RBAC 模型 +- 丰富的内置业务组件和常用页面模板 + +## 浏览器支持 +| [Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | +| --- | --- | --- | --- | --- | +| last 2 versions | last 2 versions | last 2 versions | last 2 versions | last 2 versions | +## 环境要求 + +|git|node|yarn| +|---|----|----| +|`^2.15.0`|`^16.14.0`|`^1.21.1`| + +## 使用 + +### 拉取代码 + +```bash +git clone https://github.com/stepui/stepin-template.git +``` + +### 安装依赖 + +```bash +yarn install +``` + +### 启动 + +``` +yarn dev +``` + +### 预览 + +启动成功后,控制台会显示本地访问地址:http://127.0.0.1:5173,浏览器打开即可预览。 + +更新多信息请参考 [使用文档](http://stepui.gitee.io/stepin-template-docs/) + +## 参与贡献 + +我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :star2:: + +- 在你的公司或个人项目中使用 Stepin Template。 +- 通过 [Issue](https://github.com/stepui/stepin-template/issues/new) 报告:bug:或进行咨询。 +- 提交 [Pull Request](https://github.com/stepui/stepin-template/pulls) 改进 Stepin Template 的代码。 +- 加入社群,与小伙伴们一同交流心得。QQ 群:441231578 + +## 打赏作者 +如果该项目对您有所帮助,可以请作者喝一杯咖啡。 +

+ + +

diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000..8ee0382 --- /dev/null +++ b/app/README.md @@ -0,0 +1,84 @@ +

Stepin Template

+ +
+ +开箱即用的中后台前端/设计解决方案 +
+(原[ Vue Antd Admin](https://github.com/iczer/vue-antd-admin/) Vue3 版本) + +![GitHub package.json version (subfolder of monorepo)](https://img.shields.io/github/package-json/v/stepui/stepin-template) +![GitHub repo size](https://img.shields.io/github/repo-size/stepui/stepin-template) +![GitHub last commit](https://img.shields.io/github/last-commit/stepui/stepin-template) +![GitHub release (latest by date)](https://img.shields.io/github/v/release/stepui/stepin-template) +![Docs by iczer](https://img.shields.io/badge/docs%20by-iczer-green) + +![preview](./docs/images/preview.png) + +
+ +简体中文 | [English](./README-en_US.md) + +- 预览地址: https://stepui.gitee.io/stepin-template +- 使用文档: http://stepui.gitee.io/stepin-template-docs/ +- 常见问题: http://stepui.gitee.io/stepin-template-docs/issue +- 国内镜像: https://gitee.com/stepui/stepin-template + +## 特性 + +- 强大的主题定制功能,实时动态切换 +- 多页签功能,助力后台管理高效开发 +- 内置权限控制,兼容 ABAC 和 RBAC 模型 +- 丰富的内置业务组件和常用页面模板 + +## 浏览器支持 +| [Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| last 2 versions | last 2 versions | last 2 versions | last 2 versions | last 2 versions | +## 环境要求 + +| git | node | yarn | +| --------- | ---------- | --------- | +| `^2.15.0` | `^16.14.0` | `^1.21.1` | + +## 使用 + +### 拉取代码 + +```sh +git clone https://github.com/stepui/stepin-template.git +``` + +### 安装依赖 + +```sh +yarn install +``` + +### 启动 + +```sh +yarn dev +``` + +### 预览 + +启动成功后,控制台会显示本地访问地址:http://127.0.0.1:5173,浏览器打开即可预览。 + +更新多信息请参考 [使用文档](http://stepui.gitee.io/stepin-template-docs/) + +## 参与贡献 + +我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :star2:: + +- 在你的公司或个人项目中使用 Stepin Template。 +- 通过 [Issue](https://github.com/stepui/stepin-template/issues/new) 报告:bug:或进行咨询。 +- 提交 [Pull Request](https://github.com/stepui/stepin-template/pulls) 改进 Stepin Template 的代码。 +- 加入社群,与小伙伴们一同交流心得。QQ 群:441231578 +- less报错 + yarn remove less && yarn add less@4.1.3 -D +## 打赏作者 +如果该项目对您有所帮助,可以请作者喝一杯咖啡。 +

+ + +

diff --git a/app/gulpfile.babel.js/index.js b/app/gulpfile.babel.js/index.js new file mode 100644 index 0000000..4a06b30 --- /dev/null +++ b/app/gulpfile.babel.js/index.js @@ -0,0 +1,86 @@ +import { series, src, dest } from 'gulp'; +import clean from 'gulp-clean'; +import replace from 'gulp-replace'; +import vueTsc from './vueTemplateTsc'; + +export { default as tsc } from './vueTemplateTsc'; + +function cleanAll() { + return src(['./target/*', '!./target/node_modules'], { allowEmpty: true }).pipe(clean({ force: true })); +} + +function copyIndex() { + return src(['./index.html']).pipe(replace('.ts', '.js')).pipe(replace(' + TS', '')).pipe(dest('./target')); +} + +function copyPublic() { + return src(['./public/**/*']).pipe(dest('./target/public')); +} + +function copyStyleAndJs() { + return src(['./src/**/*.{less,css,js}']).pipe(dest('./target/src')); +} + +function copyAssets() { + return src(['./src/assets/**/*']).pipe(dest('./target/src/assets')); +} + +function copyConfig() { + return src([ + './.env*', + './.gitignore', + '.babelrc', + './*.{json,cjs}', + './LICENSE', + '!./tsconfig.*', + '!./package*.json', + ]).pipe(dest('./target')); +} + +function copyReadme() { + return src(['./*.md']).pipe(replace('stepin-template.git', 'stepin-template-js.git')).pipe(dest('./target')); +} + +function copyPackageJson() { + return src(['./package.json']) + .pipe(replace(/,?[\r\n]+.*"(gulp[\-\w]*|vue-tsc|typescript)"[^,\r\n]*/g, '')) + .pipe(replace(/vue-tsc --noEmit && /g, '')) + .pipe(dest('./target')); +} +function cleanJsRepository() { + return src([ + '../stepin-template-js/*', + '!../stepin-template-js/node_modules', + '!../stepin-template-js/.git', + '!../stepin-template-js/.history', + '!../stepin-template-js/.vscode', + ]).pipe(clean({ force: true })); +} +export function copyToJsRepository() { + return src([ + './target/**/*', + './.env*', + './.gitignore', + '.babelrc', + '!./target/node_modules/**/*', + '!./target/node_modules', + ]).pipe(dest('../stepin-template-js')); +} + +export function copyDocs() { + return src(['./docs/**/*', '!./docs/.vitepress/**/*', '!./docs/.vitepress']).pipe(dest('../stepin-template-js/docs')); +} + +export const makeJs = series(cleanJsRepository, copyToJsRepository, copyDocs); + +export default series( + cleanAll, + vueTsc, + copyIndex, + copyPublic, + copyStyleAndJs, + copyConfig, + copyReadme, + copyAssets, + copyPackageJson +); diff --git a/app/gulpfile.babel.js/transform.js b/app/gulpfile.babel.js/transform.js new file mode 100644 index 0000000..4165e3f --- /dev/null +++ b/app/gulpfile.babel.js/transform.js @@ -0,0 +1,19 @@ +import { Transform } from 'stream'; + +export class BufferTransform extends Transform { + constructor(transform) { + super({ + objectMode: true, + transform(file, enc, callback) { + if (file.isBuffer()) { + file.contents = Buffer.from(transform(new String(file.contents))); + } + if (file.isStream()) { + console.log('stream file', file.path); + } + return callback(null, file); + }, + }); + return this; + } +} diff --git a/app/gulpfile.babel.js/vueTemplateTsc.js b/app/gulpfile.babel.js/vueTemplateTsc.js new file mode 100644 index 0000000..040d354 --- /dev/null +++ b/app/gulpfile.babel.js/vueTemplateTsc.js @@ -0,0 +1,152 @@ +import { src, dest, series } from 'gulp'; +import rename from 'gulp-rename'; +import replace from 'gulp-replace'; +import { BufferTransform } from './transform'; + +import ts from 'typescript'; +import prettier from 'prettier'; +import tsConfig from '../tsconfig.json'; + +tsConfig.compilerOptions.sourceMap = false; +tsConfig.compilerOptions.importsNotUsedAsValues = 'preserve'; + +/** + * vue 文件代码格式化 + * @param {*} code + * @returns + */ +const vueFormatter = () => + new BufferTransform((code) => + prettier.format(code, { + trailingComma: 'es5', + parser: 'vue', + vueIndentScriptAndStyle: true, + singleQuote: true, + }) + ); +/** + * ts 文件代码格式化 + * @param {*} code + * @returns + */ +const tsFormatter = () => + new BufferTransform((code) => + prettier.format(code, { + trailingComma: 'es5', + parser: 'typescript', + singleQuote: true, + }) + ); + +/** ts编译器 */ +const tsToJs = (tsStrCode) => { + const code = tsStrCode + .replace(/defineEmits<[\s\S]*>\(\);/g, (match) => + match.replace(/'?([\w:]*)'?:\s*\[.*\];/g, "'$1',").replace(/\(e:\s*(\'[^',]*\')[^;]*;/g, '$1,') + ) + .replace(/<{([^\{\}<>]*)}>\(\)/g, '([$1])'); + const result = ts.transpileModule(code, tsConfig); + const jsStrCode = result.outputText; + return jsStrCode; +}; + +/** + * SFC 文件 ts 代码编译 + * @param {*} tmpContent + * @returns + */ +const handleTmpContent = (tmpContent) => { + if (!tmpContent) { + return tmpContent; + } + return tmpContent + .replace(/(?<=((@|:|v-)[\w\-]*="))([^"]*)(?=")/g, function (match) { + if (/\s*{[\s\S]*}\s*/.test(match)) { + return tsToJs(`const patch = ${match}`).replace(/;\s*/g, '').replace('const patch = ', ''); + } + return `${tsToJs(match).replace(/;\s*/g, '')}`; + }) + .replace(/(?<=({{))([^{}]*)(?=}})/g, function (match) { + if (/\s*{[\s\S]*}\s*/.test(match)) { + return tsToJs(`const patch = ${match}`).replace(/;\s*/g, '').replace('const patch = ', ''); + } + return `${tsToJs(match).replace(/;\s*$/, '')}`; + }); +}; + +function transformVue(options) { + return new BufferTransform((content) => + content + .replace(/(?<=(\