Repository: xpnas/yopngs Branch: master Commit: a5448bf2ae8d Files: 35 Total size: 83.6 KB Directory structure: gitextract_iuwhz8zx/ ├── .dockerignore ├── .github/ │ └── workflows/ │ ├── docker-image-release.yml │ └── docker-image.yml ├── .gitignore ├── LICENSE ├── README.md ├── docker-compose.yml ├── yopngs/ │ ├── Controllers/ │ │ └── ApiController.cs │ ├── Dockerfile │ ├── IStore/ │ │ ├── IDataStore.cs │ │ ├── IStoreCheck.cs │ │ ├── IStoreCompress.cs │ │ ├── SotreCenter.cs │ │ ├── StoreBase.cs │ │ ├── StoreCheckNsfwWarperResetAPI.cs │ │ ├── StoreCompressPngQuantWarper.cs │ │ └── StoreCompressTinyWarper.cs │ ├── Program.cs │ ├── Properties/ │ │ └── launchSettings.json │ ├── Startup.cs │ ├── Stores/ │ │ ├── B2Store.cs │ │ ├── COSStore.cs │ │ ├── DISKStore.cs │ │ ├── GithubStore.cs │ │ ├── OSSStore.cs │ │ └── S3Store.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── defaultsetting.json │ ├── wwwroot/ │ │ ├── index.html │ │ └── static/ │ │ ├── file.js │ │ └── style.css │ ├── yopngs.csproj │ └── yopngs.runtimeconfig.json └── yopngs.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ **/.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 ================================================ FILE: .github/workflows/docker-image-release.yml ================================================ name: Docker Image release on: release: types: [published] jobs: build: runs-on: ubuntu-latest steps: - name: Check out the repo uses: actions/checkout@v2 - name: Login to DockerHub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Docker Build & Push to Docker Hub uses: docker/build-push-action@v2 with: context: . file: ./yopngs/Dockerfile platforms: linux/amd64 push: true tags: xpnas/yopngs:latest - name: 'Report Suecss' run: curl ${{ secrets.INOTIFY }}/yopngs/latestIsOk! ================================================ FILE: .github/workflows/docker-image.yml ================================================ name: Docker Image master on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest steps: - name: Check out the repo uses: actions/checkout@v2 - name: Login to DockerHub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Docker Build & Push to Docker Hub uses: docker/build-push-action@v2 with: context: . file: ./yopngs/Dockerfile platforms: linux/amd64 push: true tags: xpnas/yopngs:master - name: 'Report Suecss' run: curl ${{ secrets.INOTIFY }}/yopngs/masterIsOk! ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ /iimages/iimages/appsettings.json /iimages/iimages/wwwroot/.vscode ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 xpnas 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. ================================================ FILE: README.md ================================================ # yopngs 示例站点:[有图床](https://yopngs.com) [![Docker Image master](https://github.com/xpnas/yopngs/actions/workflows/docker-image.yml/badge.svg?branch=master)](https://github.com/xpnas/yopngs/actions/workflows/docker-image.yml) 一个纯粹的开源图床,聚焦图床核心功能,抛去用户验证、上传限制,自带鉴黄功能 支持鉴黄、支持压缩、支持本地存储、COS存储、OSS存储、B2存储 ## 使用方法 ### 发布版 请先确认已安装DockerCompose ``` wget "https://raw.githubusercontent.com/xpnas/yopngs/master/docker-compose.yml" ``` ``` docker-compose up -d ``` ### 配置Nginx代理 ``` yml server { location / { proxy_pass http://localhost:8081; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } ``` ## 配置存储源 所有配置都在config目录下的setting.json文件,可参照defaultsetting.json修改 ### 本地存储 DISKStores节点,支持多个,可使用docker启动命令映射Rclone挂载的磁盘 ``` json "DISKStores": [ { "diskfloder": "/yopngs",//本地目录,docker请做映射 "webfloder": "/v1",//url目录,如https://yopngs.com/v1/2022/01/01/xxxxx.png "name": "yopngs",//主界面下拉显示名称,随意填写 "type": "yopngs",//内部类型,随意填写 "index": 0,//主界面下拉排序,越小越优先 "active": true//是否激活 }, ``` ### Backblaze2存储 B2Stores节点,支持多个 ```json "B2Stores": [ { "KeyId": "xx", "ApplicationKey": "xx", "BucketId": "xx", "Domain": "https://xx.com",//建议在B2前套上Cloudflare,使用自定义域名 "Safe":false,//建议使用Cloudflare规则以避免暴露B2信息,防止有心人刷B2流量,开启后将去除Url中的file/BucketName "name": "backblazeb2", "type": "backblazeb2", "index": "2", "active": true } ``` ### 腾讯COS存储 COSStores节点,支持多个 ``` json "COSStores": [ { "region": "ap-shanghai", "bucket": "xx", "SECRET_ID": "xx", "SECRET_KEY": "xx", "Domain": "https://xx.com", "name": "COS", "type": "COS", "index": 1, "active": false } ], ``` ### 阿里OSS存储 OSSStores节点,支持多个 ``` json "OSSStores": [ { "AccessKeyId": "xxx", "AccessKeySecret": "xx", "Endpoint": "xx", "Domain": "https://xx.com", "name": "OSS", "type": "OSS", "index": "2", "active": false } ], ``` ## 其他设置 ```json "GLOBAL": { "SIZELIMIT": 30,//图片大小 "EXTLIMIT": ".PNG.GIF.JPG.JPEG.BMP",//类型限制 "NSFW": true,//鉴黄开关 "NSFWCORE": 0.5,//鉴黄分数0~1 "NSFWHOST": "http://nsfwapi:5000",//请勿修改 "SERVERHOST": "http://yopngs:80",//请勿修改 "COMPRESS": false,//是否启用压缩 "COUNT": 0, "STARTDATE": "2020.01.01" }, ``` ================================================ FILE: docker-compose.yml ================================================ version: '2' services: yopngs: container_name: yopngs restart: always image: xpnas/yopngs:latest volumes: - /yopngs:/yopngs - /yopngs_config:/app/config ports: - "8081:80" networks: - yopngs nsfwapi: container_name: nsfwapi restart: always image: eugencepoi/nsfw_api:latest environment: PORT: 5000 links: - yopngs ports: - "8082:5000" networks: - yopngs networks: yopngs: driver: bridge ================================================ FILE: yopngs/Controllers/ApiController.cs ================================================ using Iimages.IStore; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using System; using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Threading.Tasks; namespace Iimages.Controllers { [ApiController] [Route("[controller]")] public class ApiController : ControllerBase { private static object CountValue = ""; private static object DateValue = ""; private IHostEnvironment mEenvironment; public ApiController(IHostEnvironment hostEnvironment) { mEenvironment = hostEnvironment; CountValue = GetCountValue(false); } [HttpPost] [Route("upload")] public async Task UploadAsync(string type) { return await Task.Run(() => { //类型检测 IDataStore store; if (!SotreCenter.Stores.TryGetValue(type, out store)) { return Ok(new { UploadNode = type, code = 404, data = new { msg = "ERROR", name = "ERROR", url = "ERROR", }, msg = string.Format("上传失败!不支持的类型{0}!", type) }); } //文件数量检测 var form = Request.Form; var files = form.Files; if (files.Count != 1) { return Ok(new { UploadNode = type, code = 404, data = new { msg = "ERROR", name = "ERROR", url = "ERROR", }, msg = "上传失败!" }); } //文件大小检测 var formFile = files[0]; var sizeLimit = Convert.ToInt32(SotreCenter.Config["GLOBAL:SIZELIMIT"]); if (formFile.Length < 0 || formFile.Length > sizeLimit * 1024 * 1024) { return Ok(new { UploadNode = type, code = 404, data = new { msg = "ERROR", name = "ERROR", url = "ERROR", }, msg = string.Format("上传失败!限制大小{0}-{1}M", 0, sizeLimit) }); } //格式检测 var sourceName = formFile.FileName; var fileExt = Path.GetExtension(sourceName); var extLimit = SotreCenter.Config["GLOBAL:EXTLIMIT"]; if (!extLimit.Contains(fileExt.ToUpper())) { return Ok(new { UploadNode = type, code = 404, data = new { msg = "ERROR", name = "ERROR", url = "ERROR", }, msg = string.Format("上传失败!限制格式{0}", 0, extLimit) }); } try { //黄图检测 var localName = Guid.NewGuid().ToString() + fileExt; var webServer = Request.Scheme + "://" + Request.Host; var nswFilePath = string.Empty; using (var memoery = new MemoryStream()) { memoery.SetLength(formFile.Length); formFile.CopyTo(memoery); var maps = memoery.GetBuffer(); //压缩 if (SotreCenter.COMPRESS && (fileExt.ToUpper() == ".PNG" || fileExt.ToUpper() == ".JPG")) { maps = SotreCenter.StoreCompress.Compress(maps); } //鉴黄 if (SotreCenter.NSFW && !SotreCenter.NSFWCHECK.PassSex(maps)) { return Ok(new { UploadNode = type, code = 404, data = new { msg = "ERROR", name = sourceName, url = "ERROR", }, msg = "小撸怡情,大撸伤身!" }); } //存储 var cdnUrl = string.Empty; if (store.Up(maps, localName, webServer, ref cdnUrl)) { CountValue = GetCountValue(true); return Ok(new { UploadNode = type, code = 200, data = new { msg = "OK", name = sourceName, url = cdnUrl, }, msg = "上传成功!" }); } return Ok(new { UploadNode = type, code = 502, data = new { msg = "ERROR", name = sourceName, url = "ERROR", }, msg = "上传失败!" }); } } catch { return Ok(new { UploadNode = type, code = 502, data = new { msg = "ERROR", name = sourceName, url = "ERROR", }, msg = "上传失败!" }); } }); } [HttpGet] [Route("supports")] public List GetSupports() { return SotreCenter.Supports; } [HttpGet] [Route("info")] public string Info() { var startDate = DateTime.ParseExact(GetDateValue() as string, "yyyyMMdd", System.Globalization.CultureInfo.CurrentCulture); var timeSpan = DateTime.Now - startDate; return string.Format("本站已运行 {0} 天,托管 {1} 张图片", timeSpan.Days, Convert.ToInt64(CountValue)); } [HttpGet] public string Get() { return "It's Work"; } private object GetCountValue(bool add) { lock (CountValue) { var filePath = Path.Combine(mEenvironment.ContentRootPath, "config/count.txt"); if (!System.IO.File.Exists(filePath)) System.IO.File.WriteAllText(filePath, "0"); CountValue = System.IO.File.ReadAllText(filePath); if (add) { CountValue = (Convert.ToInt64(CountValue) + 1).ToString(); System.IO.File.WriteAllText(filePath, CountValue as string); } } return CountValue; } private object GetDateValue() { lock (DateValue) { if (string.IsNullOrEmpty(DateValue as string)) { var filePath = Path.Combine(mEenvironment.ContentRootPath, "config/date.txt"); if (!System.IO.File.Exists(filePath)) System.IO.File.WriteAllText(filePath, DateTime.Now.ToString("yyyyMMdd")); DateValue = System.IO.File.ReadAllText(filePath); } } return DateValue; } } } ================================================ FILE: yopngs/Dockerfile ================================================ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. FROM mcr.microsoft.com/dotnet/aspnet:3.1 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build WORKDIR /src COPY ["yopngs/yopngs.csproj", "yopngs/"] RUN dotnet restore "yopngs/yopngs.csproj" COPY . . WORKDIR "/src/yopngs" RUN dotnet build "yopngs.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "yopngs.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "yopngs.dll"] ================================================ FILE: yopngs/IStore/IDataStore.cs ================================================ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; namespace Iimages.IStore { public class SupportType { public string Type { get; set; } public string Name { get; set; } public int Index { get; set; } public SupportType(IDataStore store) { Name = store.Name; Type = store.Type; Index = store.Index; } } public interface IDataStore { int Index { get; set; } string Name { get; set; } string Type { get; set; } bool Up(byte[] maps, string localName, string localUrl, ref string cdnUrl); } public interface IStoreFactory { IDataStore[] GetStores(IApplicationBuilder app, IConfiguration configuration); } } ================================================ FILE: yopngs/IStore/IStoreCheck.cs ================================================ namespace Iimages.IStore { public interface IStoreCheck { bool PassSex(byte[] formFile); } } ================================================ FILE: yopngs/IStore/IStoreCompress.cs ================================================ namespace Iimages.IStore { public interface IStoreCompress { byte[] Compress(byte[] maps); } } ================================================ FILE: yopngs/IStore/SotreCenter.cs ================================================ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Reflection; namespace Iimages.IStore { public static class SotreCenter { static SotreCenter() { StoreFactories = Assembly.GetExecutingAssembly() .GetTypes() .Where(e => e.GetInterfaces().Contains(typeof(IStoreFactory))) .Select(e => Activator.CreateInstance(e)) .OfType() .ToList(); } public static void Initialize(IApplicationBuilder app, IWebHostEnvironment env, IConfiguration configuration, IHttpClientFactory httpClientFactory) { var stores = StoreFactories.SelectMany(e => e.GetStores(app, configuration)).ToList(); Config = configuration; Stores = stores.ToDictionary(e => e.Type, e => e); Supports = stores.Select(e => new SupportType(e)).OrderBy(e => e.Index).ToList(); NSFW = configuration.GetSection("GLOBAL").GetValue("NSFW"); COMPRESS = configuration.GetSection("GLOBAL").GetValue("COMPRESS"); NSFWHOST = configuration.GetSection("GLOBAL").GetValue("NSFWHOST"); if (NSFW) { NSFWCHECK = new StoreCheckNsfwWarperResetAPI(app, env, configuration, httpClientFactory); } if (COMPRESS) { StoreCompress = new StoreCompressTinyWarper(app, env, configuration, httpClientFactory); } } public static IConfiguration Config { get; private set; } public static bool NSFW { get; private set; } public static string NSFWHOST { get; private set; } public static bool COMPRESS { get; private set; } public static List StoreFactories { get; private set; } public static Dictionary Stores { get; private set; } public static List Supports { get; private set; } public static IStoreCheck NSFWCHECK { get; private set; } public static IStoreCompress StoreCompress { get; private set; } } } ================================================ FILE: yopngs/IStore/StoreBase.cs ================================================ namespace Iimages.IStore { public abstract class StoreBase : IDataStore { public int Index { get; set; } public string Name { get; set; } public string Type { get; set; } public abstract bool Up(byte[] maps, string localName, string localUrl, ref string cdnUrl); } } ================================================ FILE: yopngs/IStore/StoreCheckNsfwWarperResetAPI.cs ================================================ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.FileProviders; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Runtime.InteropServices; using System.Threading.Tasks; namespace Iimages.IStore { public class StoreCheckNsfwWarperResetAPI : IStoreCheck { private readonly string NSFWFLODER; private readonly IHttpClientFactory HttpClientFactory; private readonly string SERVERHOST; private readonly string NSFWHOST; private readonly double NSFWCORE; public StoreCheckNsfwWarperResetAPI(IApplicationBuilder app, IWebHostEnvironment env, IConfiguration config, IHttpClientFactory httpClientFactory) { HttpClientFactory = httpClientFactory; if (config.GetSection("GLOBAL") != null) { NSFWCORE = Convert.ToDouble(config["GLOBAL:NSFWCORE"]); NSFWHOST = Convert.ToString(config["GLOBAL:NSFWHOST"]); SERVERHOST = Convert.ToString(config["GLOBAL:SERVERHOST"]); NSFWFLODER = Path.Combine(env.ContentRootPath, "nsfw"); if (!Directory.Exists(NSFWFLODER)) { Directory.CreateDirectory(NSFWFLODER); } app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(NSFWFLODER), RequestPath = "/nsfw" }); } } public bool PassSex(byte[] formFile) { //保存到鉴黄目录 var localName = Guid.NewGuid().ToString() + ".png"; var nsfwFile = string.Format("{0}/{1}", NSFWFLODER, localName); File.WriteAllBytes(nsfwFile, formFile); try { var nsfwFileUrl = string.Format("{0}/{1}/{2}", SERVERHOST, "nsfw", localName); var url = string.Format("{0}{1}", NSFWHOST, "/?url=" + nsfwFileUrl); var request = new HttpRequestMessage(HttpMethod.Get, url); var response = HttpClientFactory.CreateClient().SendAsync(request).Result; var result = response.Content.ReadAsStringAsync().Result; var jsonDoc = System.Text.Json.JsonDocument.Parse(result); var jsonScore = jsonDoc.RootElement.GetProperty("score"); if (jsonScore.GetDouble() < NSFWCORE) return true; return false; } catch { return false; } finally { File.Delete(nsfwFile); } } } } ================================================ FILE: yopngs/IStore/StoreCompressPngQuantWarper.cs ================================================ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Minimage; namespace Iimages.IStore { public class StoreCompressPngQuantWarper : IStoreCompress { private static PngQuant g_PngQuant; static StoreCompressPngQuantWarper() { var options = new PngQuantOptions() { QualityMinMax = (65, 80), IEBug = false, Bit = 256 }; g_PngQuant = new PngQuant(options); } public StoreCompressPngQuantWarper(IApplicationBuilder app, IConfiguration config) { } public byte[] Compress(byte[] maps) { try { return g_PngQuant.Compress(maps).Result; } catch { return maps; } } } } ================================================ FILE: yopngs/IStore/StoreCompressTinyWarper.cs ================================================ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Minimage; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; namespace Iimages.IStore { public class StoreCompressTinyWarper : IStoreCompress { private readonly IHttpClientFactory HttpClientFactory; public StoreCompressTinyWarper(IApplicationBuilder app, IWebHostEnvironment env, IConfiguration config, IHttpClientFactory httpClientFactory) { HttpClientFactory = httpClientFactory; } public byte[] Compress(byte[] maps) { try { var randomIP = string.Format("{0}.{1},{2},{3}", new Random().Next(1, 254), new Random().Next(1, 254), new Random().Next(1, 254), new Random().Next(1, 254)); var webRequest = WebRequest.Create(new Uri("https://tinypng.com/backend/opt/shrink")); webRequest.Method = "Post"; webRequest.ContentType = "application/x-www-form-urlencoded"; webRequest.Headers.Add("rejectUnauthorized", "false"); webRequest.Headers.Add("Postman-Token", DateTime.Now.ToString()); webRequest.Headers.Add("Cache-Control", "no-cache"); webRequest.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"); webRequest.Headers.Add("X-Forwarded-For", randomIP); using (var postStream = webRequest.GetRequestStream()) { var requestStream = webRequest.GetRequestStream(); requestStream.Write(maps, 0, maps.Length); } var response = webRequest.GetResponse(); using (Stream stream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { string resuleJson = reader.ReadToEnd(); var compressUrl = JsonDocument.Parse(resuleJson).RootElement.GetProperty("output").GetProperty("url").GetString(); using (WebClient client = new WebClient()) { using (Stream compressStream = client.OpenRead(compressUrl)) { int readCount = 0; int bufferSize = 1 << 17; var buffer = new byte[bufferSize]; using (var memory = new MemoryStream()) { while ((readCount = compressStream.Read(buffer, 0, bufferSize)) > 0) { memory.Write(buffer, 0, readCount); } return memory.ToArray(); } } } } } } catch(Exception ex) { return maps; } } } } ================================================ FILE: yopngs/Program.cs ================================================ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; namespace Iimages { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } } } ================================================ FILE: yopngs/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:64028", "sslPort": 44396 } }, "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "api", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "Docker": { "commandName": "Docker", "launchBrowser": true, "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/api", "publishAllPorts": true, "useSSL": true }, "yopngs": { "commandName": "Project", "launchBrowser": true, "launchUrl": "index.html", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "https://localhost:5001;http://localhost5000" } } } ================================================ FILE: yopngs/Startup.cs ================================================ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.IO; using System.Net.Http; namespace Iimages { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHttpClientFactory httpClientFactory) { app.UseCors(options => { options.AllowAnyHeader(); options.AllowAnyMethod(); options.AllowAnyOrigin(); }); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthorization(); app.UseStaticFiles(); app.UseFileServer(); if (!Directory.Exists(Path.Combine(env.ContentRootPath, "config"))) Directory.CreateDirectory(Path.Combine(env.ContentRootPath, "config")); var filePath = Path.Combine(env.ContentRootPath, "config/setting.json"); if (!File.Exists(filePath)) File.Copy(Path.Combine(env.ContentRootPath, "defaultsetting.json"), filePath); var config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("config/setting.json", optional: true, reloadOnChange: true) .Build(); IStore.SotreCenter.Initialize(app, env,config, httpClientFactory); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } } ================================================ FILE: yopngs/Stores/B2Store.cs ================================================ using B2Net; using B2Net.Models; using Iimages.IStore; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.Linq; namespace Iimages.Stores { public class B2Store : StoreBase { private bool mPersistBucket, mSafe; private string mKeyId, mApplicationKey, mBucketId, mDomain; public B2Store(string keyId, string applicationKey, string bucketId, bool prsistBucket, string domain,bool safe) { mKeyId = keyId; mApplicationKey = applicationKey; mBucketId = bucketId; mPersistBucket = prsistBucket; mDomain = domain; mSafe = safe; } public override bool Up(byte[] maps, string localName, string localUrl, ref string cdnUrl) { try { var mOptions = new B2Options() { KeyId = mKeyId, ApplicationKey = mApplicationKey, BucketId = mBucketId, PersistBucket = mPersistBucket }; var mClient = new B2Client(mOptions); var result = mClient.Authorize().Result; if (result.Authenticated) { var timeTag = DateTime.Now.ToString("yyyy/MM/dd/"); var uploadUrl = mClient.Files.GetUploadUrl(mBucketId).Result; var fileInfo = mClient.Files.Upload(maps, timeTag + localName, uploadUrl).Result; var fileId = fileInfo.FileId; if (mSafe) { cdnUrl = string.Format("{0}/{1}", mDomain, timeTag + localName); } else { cdnUrl = string.Format("{0}/file/{1}/{2}", mDomain, result.Capabilities.BucketName, timeTag + localName); } return true; } return false; } catch { return false; } } } public class B2StoreFactory : IStoreFactory { public IDataStore[] GetStores(IApplicationBuilder app, IConfiguration configuration) { var results = new List(); var selection = configuration.GetSection("B2Stores"); foreach (IConfigurationSection section in selection.GetChildren()) { var active = section.GetValue("active"); if (active) { var keyId = section.GetValue("KeyId"); var applicationKey = section.GetValue("ApplicationKey"); var bucketId = section.GetValue("BucketId"); var persistBucket = section.GetValue("PersistBucket"); var domain = section.GetValue("Domain"); var safe = section.GetValue("Safe"); var index = section.GetValue("index"); var name = section.GetValue("name"); var type = section.GetValue("type"); var store = new B2Store(keyId, applicationKey, bucketId, persistBucket, domain, safe) { Name = name, Type = type, Index = index }; results.Add(store); } } return results.ToArray(); } } } ================================================ FILE: yopngs/Stores/COSStore.cs ================================================ using COSXML; using COSXML.Auth; using COSXML.Transfer; using Iimages.IStore; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; namespace Iimages.Stores { public class COSStore : StoreBase { private CosXml mCosXml; private string mBucket, mDomain; public COSStore(string bucket, string region, string secretId, string secretKey,string domain) { long durationSecond = 600; var config = new CosXmlConfig.Builder().IsHttps(true).SetRegion(region).SetDebugLog(true).Build(); QCloudCredentialProvider cosCredentialProvider = new DefaultQCloudCredentialProvider(secretId, secretKey, durationSecond); mCosXml = new CosXmlServer(config, cosCredentialProvider); mBucket = bucket; mDomain = domain; } public override bool Up(byte[] maps, string localName, string localUrl, ref string cdnUrl) { try { var cosPath = DateTime.Now.ToString("/yyyy/MM/dd/") + localName; var putObjectRequest = new COSXML.Model.Object.PutObjectRequest(mBucket, cosPath, maps); var uploadTask = new COSXMLUploadTask(new COSXML.Model.Object.PutObjectRequest(mBucket, cosPath, maps)); COSXML.Model.Object.PutObjectResult result = mCosXml.PutObject(putObjectRequest); if (result.IsSuccessful()) { cdnUrl = string.Format("{0}{1}", mDomain, cosPath); return true; } } catch (Exception e) { Console.WriteLine("CosException: " + e); } return false; } } public class CosStoreFactory : IStoreFactory { public IDataStore[] GetStores(IApplicationBuilder app, IConfiguration configuration) { var results = new List(); var selection = configuration.GetSection("COSStores"); foreach (IConfigurationSection section in selection.GetChildren()) { var active = section.GetValue("active"); if (active) { var region = section.GetValue("region"); var bucket = section.GetValue("bucket"); var secretId = section.GetValue("SECRET_ID"); var secretKey = section.GetValue("SECRET_KEY"); var domain = section.GetValue("Domain"); var index = section.GetValue("index"); var name = section.GetValue("name"); var type = section.GetValue("type"); var store = new COSStore(bucket, region, secretId, secretKey, domain) { Name = name, Type = type, Index = index }; results.Add(store); } } return results.ToArray(); } } } ================================================ FILE: yopngs/Stores/DISKStore.cs ================================================ using Iimages.IStore; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.FileProviders; using System; using System.Collections.Generic; using System.IO; namespace Iimages.Stores { public class DISKStore : StoreBase { private string m_diskFloder, m_webFloder, m_host; public DISKStore(string diskFloder, string webFloder, string host) { m_diskFloder = diskFloder; m_webFloder = webFloder; m_host = host; } public override bool Up(byte[] maps, string localName, string webServer, ref string cdnUrl) { if (!Directory.Exists(m_diskFloder)) Directory.CreateDirectory(m_diskFloder); var timeTag = DateTime.Now.ToString("/yyyy/MM/dd/"); var filePath = m_diskFloder + timeTag + localName; var fileDir = Path.GetDirectoryName(filePath); if (!Directory.Exists(fileDir)) Directory.CreateDirectory(fileDir); File.WriteAllBytes(filePath,maps); cdnUrl = string.Format("{0}{1}{2}{3}", string.IsNullOrEmpty(m_host) ? webServer : m_host, m_webFloder, timeTag, localName); return true; } } public class DiskStoreFactory : IStoreFactory { public IDataStore[] GetStores(IApplicationBuilder app, IConfiguration configuration) { var results = new List(); var selection = configuration.GetSection("DISKStores"); foreach (IConfigurationSection section in selection.GetChildren()) { var diskfloder = section.GetValue("diskfloder"); var webfloder = section.GetValue("webfloder"); var host= section.GetValue("host"); if (!Directory.Exists(diskfloder)) Directory.CreateDirectory(diskfloder); app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(diskfloder), RequestPath = webfloder }); var active = section.GetValue("active"); if (active) { var name = section.GetValue("name"); var type = section.GetValue("type"); var index = section.GetValue("index"); var store = new DISKStore(diskfloder, webfloder,host) { Name = name, Type = type, Index = index }; results.Add(store); } } return results.ToArray(); } } } ================================================ FILE: yopngs/Stores/GithubStore.cs ================================================ using Iimages.IStore; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Octokit; using System.Collections.Generic; using System.IO; namespace Iimages.Stores { public class GithubStore : StoreBase { public override bool Up(byte[] maps, string localName, string localUrl, ref string cdnUrl) { var client = new GitHubClient(new ProductHeaderValue("my-cool-app")); var basicAuth = new Credentials("username", "password"); // NOTE: not real credentials client.Credentials = basicAuth; using (var archiveContents = File.OpenRead("output.zip")) { // TODO: better sample var assetUpload = new ReleaseAssetUpload() { FileName = "my-cool-project-1.0.zip", ContentType = "application/zip", RawData = archiveContents }; var repository = client.Repository.Release.Get("octokit", "octokit.net", 1).Result; var asset = client.Repository.Release.UploadAsset(repository, assetUpload).Result; } return true; } } public class GithubFactory : IStoreFactory { public IDataStore[] GetStores(IApplicationBuilder app, IConfiguration configuration) { var results = new List(); var selection = configuration.GetSection("OSSStores"); foreach (IConfigurationSection section in selection.GetChildren()) { //var active = section.GetValue("active"); //if (active) //{ // var acessKeyId = section.GetValue("AccessKeyId"); // var acessKeySecret = section.GetValue("AccessKeySecret"); // var endPoint = section.GetValue("Endpoint"); // var bucket = section.GetValue("Bucket"); // var domain = section.GetValue("Domain"); // var index = section.GetValue("index"); // var name = section.GetValue("name"); // var type = section.GetValue("type"); // var diskStore = new OSSStore(acessKeyId, acessKeySecret, endPoint, bucket, domain) // { // Name = name, // Type = type, // Index = index // }; // results.Add(diskStore); //} } return results.ToArray(); } } } ================================================ FILE: yopngs/Stores/OSSStore.cs ================================================ using Aliyun.OSS; using Iimages.IStore; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.IO; namespace Iimages.Stores { public class OSSStore : StoreBase { private OssClient mOssClient; private string mDomain; public string Bucket { get; private set; } public OSSStore(string accessKeyId, string accessKeySecret, string endpoint, string bucket, string domian) { Bucket = bucket; mDomain = domian; mOssClient = new OssClient(endpoint, accessKeyId, accessKeySecret); } public override bool Up(byte[] maps,string localName, string localUrl, ref string cdnUrl) { using (var stream = new MemoryStream(maps)) { try { var cosPath = DateTime.Now.ToString("yyyy/MM/dd/") + localName; mOssClient.PutObject(Bucket, cosPath, stream); cdnUrl = string.Format("{0}/{1}", mDomain, cosPath); return true; } catch { return false; } } } } public class OSSFactory : IStoreFactory { public IDataStore[] GetStores(IApplicationBuilder app, IConfiguration configuration) { var results = new List(); var selection = configuration.GetSection("OSSStores"); foreach (IConfigurationSection section in selection.GetChildren()) { var active = section.GetValue("active"); if (active) { var acessKeyId = section.GetValue("AccessKeyId"); var acessKeySecret = section.GetValue("AccessKeySecret"); var endPoint = section.GetValue("Endpoint"); var bucket = section.GetValue("Bucket"); var domain = section.GetValue("Domain"); var index = section.GetValue("index"); var name = section.GetValue("name"); var type = section.GetValue("type"); var store = new OSSStore(acessKeyId, acessKeySecret, endPoint, bucket,domain) { Name = name, Type = type, Index = index }; results.Add(store); } } return results.ToArray(); } } } ================================================ FILE: yopngs/Stores/S3Store.cs ================================================ using Amazon.S3; using Amazon.S3.Model; using Iimages.IStore; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using System.Collections.Generic; namespace Iimages.Stores { public class S3Store : StoreBase { AmazonS3Client Client; string Domain; public S3Store(string secretID, string secretKey, string region, string domain) { Domain = domain; var endPoint = new AmazonS3Config(); endPoint.ServiceURL = region; // endPoint.AuthenticationRegion = region; Client = new AmazonS3Client(secretID, secretKey, endPoint); } public override bool Up(byte[] maps, string localName, string localUrl, ref string cdnUrl) { var files= Client.ListBucketsAsync().Result ; var putRequest2 = new PutObjectRequest { BucketName = "xpnas-assets", //获取和设置键属性。此键用于标识S3中的对象,上传到s3的路径+文件名, //S3上没有文件夹可以创建一个,参考https://www.cnblogs.com/web424/p/6840207.html Key = localName, //所有者获得完全控制权,匿名主体被授予读访问权。如果 //此策略用于对象,它可以从浏览器中读取,无需验证 CannedACL = S3CannedACL.PublicRead, //上传的文件路径 FilePath = @"C:\Users\Administrator\Desktop\favicon.png", //为对象设置的标记。标记集必须编码为URL查询参数 TagSet = new List{ new Tag { Key = "Test", Value = "S3Test"} } //ContentType = "image/png" }; // putRequest2.Metadata.Add("x-amz-meta-title", "AwsS3Net"); PutObjectResponse response2 = Client.PutObjectAsync(putRequest2).Result; return false; } } public class S3StoreFactory : IStoreFactory { public IDataStore[] GetStores(IApplicationBuilder app, IConfiguration configuration) { var results = new List(); var selection = configuration.GetSection("S3Stores"); foreach (IConfigurationSection section in selection.GetChildren()) { var active = section.GetValue("active"); if (active) { var secretID = section.GetValue("secretID"); var secretKey = section.GetValue("secretKey"); var region = section.GetValue("region"); var domain = section.GetValue("Domain"); var index = section.GetValue("index"); var name = section.GetValue("name"); var type = section.GetValue("type"); var store = new S3Store(secretID, secretKey, region, domain) { Name = name, Type = type, Index = index }; results.Add(store); } } return results.ToArray(); } } } ================================================ FILE: yopngs/appsettings.Development.json ================================================ { "AllowedHosts": "*", "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } } ================================================ FILE: yopngs/appsettings.json ================================================ { "AllowedHosts": "*", "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } } ================================================ FILE: yopngs/defaultsetting.json ================================================ { "GLOBAL": { "SIZELIMIT": 30, "EXTLIMIT": ".PNG.GIF.JPG.JPEG.BMP", "NSFW": true, "NSFWCORE": 0.5, "NSFWHOST": "http://nsfwapi:5000", "SERVERHOST": "http://yopngs:80", "COMPRESS": false, "COUNT": 0, "STARTDATE": "2020.01.01" }, "DISKStores": [ { "diskfloder": "/yopngs", "webfloder": "/v1", "name": "yopngs", "type": "yopngs", "index": 0, "active": true } ] //"B2Stores": [ // { // "KeyId": "xx", // "ApplicationKey": "xx", // "BucketId": "xx", // "Domain": "xx", // "Safe"": false // "name": "backblazeb2", // "type": "backblazeb2", // "index": "2", // "active": true // } //"OSSStores": [ // { // "AccessKeyId": "ap-shanghai", // "AccessKeySecret": "xx-xx", // "Endpoint": "xx", // "Domain": "https://xx.com", // "name": "OSS", // "type": "OSS", // "index": "2", // "active": false // } //], //"COSStores": [ // { // "region": "ap-shanghai", // "bucket": "xx-xx", // "SECRET_ID": "xx", // "SECRET_KEY": "xx", // "Domain": "https://xx.com", // "name": "COS", // "type": "COS", // "index": 1, // "active": false // } //], } ================================================ FILE: yopngs/wwwroot/index.html ================================================ 有图床
开始上传

点击上传 / 粘贴上传 / 拖拽上传

上传列表
================================================ FILE: yopngs/wwwroot/static/file.js ================================================ /* * @xkx */ $(() => { let host = window.location.origin + '/api'; //$(document).ready(function () { $.ajax({ url: host + '/supports', type: 'get', success: (res) => { let selid = document.getElementById('type'); for (let i = 0; i < res.length; i++) { selid.options[i] = new Option(res[i].name, res[i].type); } }, }); $.ajax({ url: host + '/info', type: 'get', success: (res) => { let info = document.getElementById('info'); info.innerText = res; }, }); // }); /* 临时粘贴上传 */ $(document).on('paste', (event) => { let clipboardData = event.clipboardData || window.clipboardData || event.originalEvent.clipboardData; /* 判断是否支持粘贴上传 */ if (!clipboardData || !clipboardData.items) return alert('当前浏览器不支持粘贴上传'); let items = clipboardData.items; let file = null; /* 判断剪切板的内容是否是桌面类型的文件 */ if (items.length === 0) return alert('剪切板内无内容或不支持桌面文件'); /* 开始循环剪切板的内容,判断是否是文件类型,如果是文件类型,则push */ for (let i = 0; i < items.length; i++) { if (items[i].type.indexOf('image') !== -1) { file = items[i].getAsFile(); } } if (!file) return alert('剪切板内无内容或不支持桌面文件'); upload(new Array(file)); }); /* 点击上传 */ $('.upload .content').on('click', function () { $('#file').click(); }); /* 监听点击上传 */ $('#file').on('change', () => { upload($('#file')[0].files); }); /* 拖拽上传 */ $('#dragbox').on('dragover', (e) => { e.preventDefault(); }); $('#dragbox').on('dragenter', (e) => { e.preventDefault(); $('.upload').addClass('dragenter'); }); $('#dragbox').on('dragleave', (e) => { e.preventDefault(); $('.upload').removeClass('dragenter'); }); $('#dragbox').on('drop', (e) => { e.preventDefault(); $('.upload').removeClass('dragenter'); let files = e.originalEvent.dataTransfer.files; upload(files); }); /* 上传函数 */ function upload(files) { //if ($('#type').val().trim() === '') return alert('请输入'); for (let i = 0; i < files.length; i++) { var animateimg = files[i].name; var imgarr = animateimg.split('\\'); var myimg = imgarr[imgarr.length - 1]; var houzui = myimg.lastIndexOf('.'); var ext = myimg.substring(houzui, myimg.length).toUpperCase(); var file = files[i]; if (!file) { return false; } var fileSize = file.size; var maxSize = 50 * 1024 * 1024; if ( ext != '.PNG' && ext != '.GIF' && ext != '.JPG' && ext != '.JPEG' && ext != '.BMP' ) { parent.alert('文件类型错误,请上传图片类型'); $('#file').val(null); return false; } else if (parseInt(fileSize) >= parseInt(maxSize)) { parent.alert('上传的文件不能超过' + maxSize / 1024 / 1024 + 'MB'); return false; } else { document.querySelector('.container').classList.add('start'); var type = $('#type').val(); var api = host + '/upload?type='; let formData = new FormData(); formData.append('image', files[i]); let randomClass = Date.now().toString(36); $('.filelist .list').append(`
${files[i].name}
SIZE:${formatBytes(files[i].size)}
`); $.ajax({ url: api + $('#type').val(), type: 'post', dataType: 'json', processData: false, contentType: false, data: formData, xhr: () => { let xhr = $.ajaxSettings.xhr(); if (!xhr.upload) return; xhr.upload.addEventListener( 'progress', (e) => { let percent = Math.floor((e.loaded / e.total) * 100); $('.' + randomClass) .find('.progress-inner') .css('width', percent + '%'); }, false ); return xhr; }, success: (res) => { if (type == 'muke') { var imgSrc = res.data.url.muke; } else if (type == 'vxichina') { var imgSrc = res.data.url.vxichina; } else if (type == 'qihoo') { var imgSrc = res.data.url.qihoo; } else if (type == 'catbox') { var imgSrc = res.data.url.catbox; } else if (type == 'gtimg') { var imgSrc = res.data.url.gtimg; } else { var imgSrc = res.data.url; } /* 清除input框 */ $('#file').val(null); if (res.code === -1) { $('.' + randomClass).fadeOut(); alert(res.data.url); } else { if (res.code === 200) { $('.' + randomClass) .find('.progress-inner') .addClass('success'); $('.' + randomClass) .find('.status-success') .show(); $('.' + randomClass) .find('.link') .attr({ href: imgSrc, target: '_blank', }); //代码链接xkx $('.' + randomClass) .find('#Imgs_url') .attr({ value: imgSrc, }); $('.' + randomClass) .find('#Imgs_html') .attr({ value: '', }); $('.' + randomClass) .find('#Imgs_Ubb') .attr({ value: '[img]' + imgSrc + '[/img]', }); $('.' + randomClass) .find('#Imgs_markdown') .attr({ value: '![](' + imgSrc + ')', }); //显示链接xkx $('.' + randomClass) .find('#show') .show(); $('.' + randomClass) .find('#show') .attr({ value: imgSrc, }); //复制所有xkx $('.copyall').show(); var tt = $('.filelist .title').html().replace('上传列表', ''); $('.filelist .title').html(tt); } else { $('.' + randomClass) .find('.progress-inner') .addClass('error'); $('.' + randomClass) .find('.status-error') .show(); $('.' + randomClass) .find('#show') .show(); $('.' + randomClass) .find('#show') .attr({ value: res.msg, }); } } }, fail: () => { $('.' + randomClass).fadeOut(); }, }); } } } /* 获取文件大小 */ function formatBytes(bytes, decimals = 2) { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + sizes[i]; } }); function del(obj) { var item = obj.parentNode.parentNode; item.parentNode.removeChild(item); } function sel(obj) { for (var i = 0; i < document.querySelectorAll('#Imgs' + obj.id).length; i++) { document.querySelectorAll('#Imgs' + obj.id)[ i ].parentElement.nextElementSibling.value = document.querySelectorAll( '#Imgs' + obj.id )[i].value; } } function copyAll(obj) { var xkx = ''; for (var i = 0; i < document.querySelectorAll('#show').length; i++) { var xkx = xkx + document.querySelectorAll('#show')[i].value + '\n'; } var txa = document.createElement('textarea'); txa.value = xkx; document.body.appendChild(txa); txa.select(); var res = document.execCommand('copy'); document.body.removeChild(txa); console.log('copy success'); console.log(xkx); if (browserRedirect()) { alert('设备类型为手机,有一定几率复制失败!请查看剪切板是否成功复制'); } } function oCopy(obj) { obj.select(); document.execCommand('Copy'); console.log(obj.value); if (browserRedirect()) { alert('设备类型为手机,有一定几率复制失败!请查看剪切板是否成功复制'); } } function browserRedirect() { var sUserAgent = navigator.userAgent.toLowerCase(); var bIsIpad = sUserAgent.match(/ipad/i) == 'ipad'; var bIsIphone = sUserAgent.match(/iphone os/i) == 'iphone os'; var bIsMidp = sUserAgent.match(/midp/i) == 'midp'; var bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) == 'rv:1.2.3.4'; var bIsUc = sUserAgent.match(/ucweb/i) == 'web'; var bIsCE = sUserAgent.match(/windows ce/i) == 'windows ce'; var bIsWM = sUserAgent.match(/windows mobile/i) == 'windows mobile'; var bIsAndroid = sUserAgent.match(/android/i) == 'android'; if ( bIsIpad || bIsIphone || bIsMidp || bIsUc7 || bIsUc || bIsCE || bIsWM || bIsAndroid ) { return 1; } } ================================================ FILE: yopngs/wwwroot/static/style.css ================================================ * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; } ::-webkit-scrollbar { width: 7px; height: 7px; } ::-webkit-scrollbar-thumb { border-radius: 3.5px; background-color: rgba(50, 50, 50, 0.3); } ::-webkit-scrollbar-track { border-radius: 3.5px; background-color: rgba(50, 50, 50, 0.1); } html { height: 100%; } @-webkit-keyframes Gradient { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } @keyframes Gradient { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } body { height: 100%; display: flex; align-items: center; justify-content: center; background: linear-gradient( -45deg, rgba(9, 69, 138, 0.2), rgba(68, 155, 255, 0.7), rgba(117, 113, 251, 0.7), rgba(68, 155, 255, 0.7), rgba(9, 69, 138, 0.2) ); background-size: 400% 400%; -webkit-animation: Gradient 15s ease infinite; animation: Gradient 15s ease infinite; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 14px; } .container { display: grid; gap: 20px; grid-template-columns: 500px 250px; grid-template-rows: 500px; } .container.start { display: grid; gap: 20px; grid-template-columns: 250px 500px; grid-template-rows: 500px; } @-webkit-keyframes slideRight { 0% { -webkit-transform: translateX(-50%); transform: translateX(-50%); } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes slideRight { 0% { -webkit-transform: translateX(-50%); transform: translateX(-50%); } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @-webkit-keyframes slideLeft { 0% { -webkit-transform: translateX(50%); transform: translateX(50%); } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes slideLeft { 0% { -webkit-transform: translateX(50%); transform: translateX(50%); } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } .upload, .filelist { background: #fff; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); overflow: hidden; } .upload .title, .filelist .title { height: 45px; line-height: 45px; border-bottom: 1px solid #f2f6fc; color: #606266; font-weight: 500; padding: 0 15px; font-size: 16px; } .upload { -webkit-animation: slideRight 1.5s; animation: slideRight 1.5s; transition: box-shadow 0.35s; } .upload .title { display: flex; justify-content: space-between; align-items: center; } .copyall button { width: 70px; height: 28px; border-radius: 14px; margin: 0 12px; background-color: #fff; } .upload .title select { height: 28px; padding: 0 5px; border-radius: 14px; } .copyall { display: inline-flex; margin: 8px; } #show { width: 475px; height: 28px; outline: none; border: 1px solid #dcdfe6; padding: 0 15px; color: #606266; border-radius: 14px; font-size: 12px; transition: border 0.35s; -webkit-appearance: none; } .upload .title input:focus { border-color: #409eff; } .upload .content { cursor: pointer; height: calc(100% - 45px); display: flex; align-items: center; justify-content: center; flex-direction: column; } .upload .content .icon { width: 80px; height: 80px; margin-bottom: 20px; } .upload .content .desc { color: #606266; } .upload.dragenter { box-shadow: 20px 20px 20px 0 rgba(0, 0, 0, 0.25); } .upload.dragenter .content > * { pointer-events: none; } .filelist { -webkit-animation: slideLeft 1.5s; animation: slideLeft 1.5s; } .filelist .list { height: calc(100% - 45px); overflow-y: auto; padding: 10px; scroll-behavior: smooth; } .filelist .list .item { margin-bottom: 10px; } .filelist .list .item:last-child { margin-bottom: 0; } .filelist .list .item .file { display: flex; align-items: center; margin-bottom: 5px; } .filelist .list .item .file .icon { width: 45px; height: 45px; margin-right: 10px; } .filelist .list .item .file .desc { flex: 1; min-width: 0; } .filelist .list .item .file .desc__name { font-size: 15px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; font-weight: 600; color: #606266; margin-bottom: 5px; } .filelist .list .item .file .desc__size { font-size: 12px; color: #909399; } .filelist .list .item .file .link { display: flex; align-items: center; justify-content: center; padding: 5px; cursor: pointer; margin-left: 10px; border-radius: 50%; transition: background 0.2s; } .filelist .list .item .file .link svg { width: 18px; height: 18px; } .filelist .list .item .file .link:hover { background: #f56c6c50; } .filelist .list .item .progress { display: flex; align-items: center; justify-content: space-between; font-size: 12px; } .filelist .list .item .progress .progress-bar { flex: 1; min-width: 0; } .filelist .list .item .progress .progress-bar .progress-inner { width: 0%; height: 2px; background: #409eff; border-radius: 1px; transition: width 0.2s; } .filelist .list .item .progress .progress-bar .progress-inner.success { background: #67c23a; } .filelist .list .item .progress .progress-bar .progress-inner.error { background: #f56c6c; } .filelist .list .item .progress .progress-status { margin-left: 5px; } .filelist .list .item .progress .progress-status .icon { display: none; width: 14px; height: 14px; } @media (max-width: 780px) { body { height: auto; padding: 5vh 0; } .container { grid-template-columns: 350px; grid-template-rows: 400px 150px; } .container.start { grid-template-columns: 350px !important; grid-template-rows: 190px 400px !important; } .copyall button { width: 50px; margin: 0 4px; } #show { width: 320px; padding: 0 11px; } } ================================================ FILE: yopngs/yopngs.csproj ================================================  netcoreapp3.1 508626fd-dc38-4a3f-9757-1caed8893c4f Linux yopngs yopngs Always PreserveNewest Always Always PreserveNewest PreserveNewest ================================================ FILE: yopngs/yopngs.runtimeconfig.json ================================================ { "runtimeOptions": { "tfm": "netcoreapp3.1", "framework": { "name": "Microsoft.AspNetCore.App", "version": "3.1.0" }, "configProperties": { "System.GC.Server": true, "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false, "System.Net.Http.UseSocketsHttpHandler": false } } } ================================================ FILE: yopngs.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.31129.286 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "yopngs", "yopngs\yopngs.csproj", "{79A1F728-0A2D-497A-9C4B-E7B741FCCF3C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {79A1F728-0A2D-497A-9C4B-E7B741FCCF3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {79A1F728-0A2D-497A-9C4B-E7B741FCCF3C}.Debug|Any CPU.Build.0 = Debug|Any CPU {79A1F728-0A2D-497A-9C4B-E7B741FCCF3C}.Release|Any CPU.ActiveCfg = Release|Any CPU {79A1F728-0A2D-497A-9C4B-E7B741FCCF3C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {69890056-9C2F-480E-B9EE-2E12395A240B} EndGlobalSection EndGlobal