├── wwwroot ├── js │ ├── site.min.js │ └── site.js ├── favicon.ico ├── static │ ├── logo.png │ ├── nebulas.png │ ├── blockchain.jpg │ ├── developer.png │ ├── hyperchain.png │ ├── logo_white.png │ ├── resume.json │ ├── n1bz1jRkWC37Ka2xZY3zQhhLn3C5PfYpG9p.txt │ ├── index.html │ └── site.css ├── lib │ ├── bootstrap │ │ ├── dist │ │ │ ├── fonts │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ ├── js │ │ │ │ └── npm.js │ │ │ └── css │ │ │ │ └── bootstrap-theme.min.css.map │ │ ├── .bower.json │ │ └── LICENSE │ ├── jquery │ │ ├── .bower.json │ │ └── LICENSE.txt │ ├── jquery-validation │ │ ├── .bower.json │ │ └── LICENSE.md │ └── jquery-validation-unobtrusive │ │ ├── .bower.json │ │ ├── jquery.validate.unobtrusive.min.js │ │ └── jquery.validate.unobtrusive.js ├── css │ ├── site.min.css │ └── site.css └── images │ ├── banner2.svg │ ├── banner1.svg │ ├── banner3.svg │ └── banner4.svg ├── Pages ├── _ViewStart.cshtml ├── _ViewImports.cshtml ├── About.cshtml ├── Index.cshtml.cs ├── Contact.cshtml.cs ├── About.cshtml.cs ├── Contact.cshtml ├── Error.cshtml.cs ├── Error.cshtml ├── _ValidationScriptsPartial.cshtml ├── _Layout.cshtml └── Index.cshtml ├── Startup.cs ├── .gitattributes ├── .gitignore ├── media ├── 314a07267ced6fd8f8cf8106d4015dca.png ├── 7303f684c55bb1f3308c3f292de0c953.png └── fbf5c1d8ff912cfa44fabc580f682bcd.png ├── appsettings.json ├── FabricApplication.csproj ├── appsettings.Development.json ├── ResumeVerification.csproj.user ├── Components ├── LogOut.tsx ├── NebulasTest.tsx ├── Footer.tsx ├── Commit.tsx ├── Register.tsx ├── Header.tsx ├── APITest.tsx ├── LogIn.tsx ├── UserCenter.tsx ├── HyperChainAPITest.tsx ├── MainPage.tsx ├── MyResume.tsx └── Veryify.tsx ├── tsconfig.json ├── Properties ├── PublishProfiles │ ├── FolderProfile2.pubxml.user │ ├── FolderProfile.pubxml.user │ ├── FolderProfile1.pubxml.user │ ├── FolderProfile.pubxml │ ├── FolderProfile2.pubxml │ └── FolderProfile1.pubxml └── launchSettings.json ├── Program.cs ├── bundleconfig.json ├── package.json ├── webpack.config.ts ├── contract.js ├── app.tsx ├── ResumeVerification.csproj ├── Nebulas.tsx ├── Hyperchain.tsx ├── Utility.ts └── README.md /wwwroot/js/site.min.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | -------------------------------------------------------------------------------- /Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /Startup.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/Startup.cs -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.js 3 | App.js 4 | /node_modules 5 | /bin 6 | /obj 7 | !contract.js 8 | -------------------------------------------------------------------------------- /wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/wwwroot/favicon.ico -------------------------------------------------------------------------------- /wwwroot/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/wwwroot/static/logo.png -------------------------------------------------------------------------------- /wwwroot/static/nebulas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/wwwroot/static/nebulas.png -------------------------------------------------------------------------------- /wwwroot/static/blockchain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/wwwroot/static/blockchain.jpg -------------------------------------------------------------------------------- /wwwroot/static/developer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/wwwroot/static/developer.png -------------------------------------------------------------------------------- /wwwroot/static/hyperchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/wwwroot/static/hyperchain.png -------------------------------------------------------------------------------- /wwwroot/static/logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/wwwroot/static/logo_white.png -------------------------------------------------------------------------------- /media/314a07267ced6fd8f8cf8106d4015dca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/media/314a07267ced6fd8f8cf8106d4015dca.png -------------------------------------------------------------------------------- /media/7303f684c55bb1f3308c3f292de0c953.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/media/7303f684c55bb1f3308c3f292de0c953.png -------------------------------------------------------------------------------- /media/fbf5c1d8ff912cfa44fabc580f682bcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/media/fbf5c1d8ff912cfa44fabc580f682bcd.png -------------------------------------------------------------------------------- /Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using FabricApplication 2 | @namespace FabricApplication.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dearkano/TrueResume/HEAD/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /FabricApplication.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Pages/About.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model AboutModel 3 | @{ 4 | ViewData["Title"] = "About"; 5 | } 6 |

@ViewData["Title"]

7 |

@Model.Message

8 | 9 |

Use this area to provide additional information.

10 | -------------------------------------------------------------------------------- /appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}#qrCode{margin:15px}@media screen and (max-width:767px){.carousel-caption{display:none}} -------------------------------------------------------------------------------- /wwwroot/static/resume.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Mikasa", 4 | "age": "18", 5 | "education": "常盘台", 6 | "paper": "0", 7 | "patent": "12" 8 | 9 | }, 10 | { 11 | "name": "田子珺", 12 | "age": "19", 13 | "education": "浙江大学", 14 | "paper": "1", 15 | "patent": "13" 16 | } 17 | ] -------------------------------------------------------------------------------- /ResumeVerification.csproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0.0 5 | FolderProfile1 6 | 7 | -------------------------------------------------------------------------------- /Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace FabricApplication.Pages 9 | { 10 | public class IndexModel : PageModel 11 | { 12 | public void OnGet() 13 | { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Components/LogOut.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export class LogOut extends React.Component<{}, {}>{ 4 | componentDidMount() { 5 | localStorage.removeItem("HCAccount"); 6 | localStorage.removeItem("HCPassword"); 7 | localStorage.removeItem("AccountSecret"); 8 | document.location.href = "/"; 9 | } 10 | render() { 11 | return
正在注销...
; 12 | } 13 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noEmitOnError": true, 5 | "removeComments": false, 6 | "sourceMap": false, 7 | "target": "es5", 8 | "jsx": "react", 9 | "lib": [ 10 | "dom", 11 | "es5", 12 | "es6" 13 | ], 14 | "allowSyntheticDefaultImports": true, 15 | "importHelpers": true 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | "wwwroot" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /Pages/Contact.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace FabricApplication.Pages 8 | { 9 | public class ContactModel : PageModel 10 | { 11 | public string Message { get; set; } 12 | 13 | public void OnGet() 14 | { 15 | Message = "Your contact page."; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pages/About.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace FabricApplication.Pages 8 | { 9 | public class AboutModel : PageModel 10 | { 11 | public string Message { get; set; } 12 | 13 | public void OnGet() 14 | { 15 | Message = "Your application description page."; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Properties/PublishProfiles/FolderProfile2.pubxml.user: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | <_PublishTargetUrl>C:\Users\VayneTian\Desktop\新建文件夹 10 | 11 | -------------------------------------------------------------------------------- /wwwroot/static/n1bz1jRkWC37Ka2xZY3zQhhLn3C5PfYpG9p.txt: -------------------------------------------------------------------------------- 1 | {"version":4,"id":"ce71ed89-346d-4446-b7bc-89bfc5e81d96","address":"n1bz1jRkWC37Ka2xZY3zQhhLn3C5PfYpG9p","crypto":{"ciphertext":"9f2b96a4f3ecefc98da67e44820288f588ef36889b376584b8d3c4532e36c5dc","cipherparams":{"iv":"ef7dc8928af0144dd5fbb9ad63d7f08e"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"a499fde21108391509591d7ced3e4ed61bb0b269711de05da6b035660f6a1dd9","n":4096,"r":8,"p":1},"mac":"6948f75ab82654efb188d2942eafcb55441dfdcf4723cc9fbe02d89894b6b592","machash":"sha3256"}} -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /Pages/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ContactModel 3 | @{ 4 | ViewData["Title"] = "Contact"; 5 | } 6 |

@ViewData["Title"]

7 |

@Model.Message

8 | 9 |
10 | One Microsoft Way
11 | Redmond, WA 98052-6399
12 | P: 13 | 425.555.0100 14 |
15 | 16 |
17 | Support: Support@example.com
18 | Marketing: Marketing@example.com 19 |
20 | -------------------------------------------------------------------------------- /Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace FabricApplication.Pages 9 | { 10 | public class ErrorModel : PageModel 11 | { 12 | public string RequestId { get; set; } 13 | 14 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 15 | 16 | public void OnGet() 17 | { 18 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /wwwroot/lib/jquery/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "main": "dist/jquery.js", 4 | "license": "MIT", 5 | "ignore": [ 6 | "package.json" 7 | ], 8 | "keywords": [ 9 | "jquery", 10 | "javascript", 11 | "browser", 12 | "library" 13 | ], 14 | "homepage": "https://github.com/jquery/jquery-dist", 15 | "version": "2.2.0", 16 | "_release": "2.2.0", 17 | "_resolution": { 18 | "type": "version", 19 | "tag": "2.2.0", 20 | "commit": "6fc01e29bdad0964f62ef56d01297039cdcadbe5" 21 | }, 22 | "_source": "git://github.com/jquery/jquery-dist.git", 23 | "_target": "2.2.0", 24 | "_originalSource": "jquery" 25 | } -------------------------------------------------------------------------------- /Components/NebulasTest.tsx: -------------------------------------------------------------------------------- 1 | import * as Nebulas from '../Nebulas'; 2 | import * as React from 'react'; 3 | import * as md5 from "md5"; 4 | export class NebulasTest extends React.Component<{}, {}>{ 5 | 6 | async test() { 7 | const nameHash = md5("田子珺"); 8 | const resume = await Nebulas.queryResume(nameHash); 9 | console.log(resume); 10 | } 11 | async test1() { 12 | const nameHash = "vayne tian"; 13 | const resume = await Nebulas.queryResume(nameHash); 14 | console.log(resume); 15 | } 16 | render() { 17 | return
; 18 | } 19 | } -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace FabricApplication 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | BuildWebHost(args).Run(); 18 | } 19 | 20 | public static IWebHost BuildWebHost(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup() 23 | .Build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bundleconfig.json: -------------------------------------------------------------------------------- 1 | // Configure bundling and minification for the project. 2 | // More info at https://go.microsoft.com/fwlink/?LinkId=808241 3 | [ 4 | { 5 | "outputFileName": "wwwroot/css/site.min.css", 6 | // An array of relative input file paths. Globbing patterns supported 7 | "inputFiles": [ 8 | "wwwroot/css/site.css" 9 | ] 10 | }, 11 | { 12 | "outputFileName": "wwwroot/js/site.min.js", 13 | "inputFiles": [ 14 | "wwwroot/js/site.js" 15 | ], 16 | // Optionally specify minification options 17 | "minify": { 18 | "enabled": true, 19 | "renameLocals": true 20 | }, 21 | // Optionally generate .map file 22 | "sourceMap": false 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:60969/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "FabricApplication": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:60970/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Wrapping element */ 7 | /* Set some basic padding to keep content from hitting the edges */ 8 | .body-content { 9 | padding-left: 15px; 10 | padding-right: 15px; 11 | } 12 | 13 | /* Carousel */ 14 | .carousel-caption p { 15 | font-size: 20px; 16 | line-height: 1.4; 17 | } 18 | 19 | /* Make .svg files in the carousel display properly in older browsers */ 20 | .carousel-inner .item img[src$=".svg"] { 21 | width: 100%; 22 | } 23 | 24 | /* QR code generator */ 25 | #qrCode { 26 | margin: 15px; 27 | } 28 | 29 | /* Hide/rearrange for smaller screens */ 30 | @media screen and (max-width: 767px) { 31 | /* Hide captions */ 32 | .carousel-caption { 33 | display: none; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Properties/PublishProfiles/FolderProfile.pubxml.user: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | <_PublishTargetUrl>C:\Users\VayneTian\Desktop 10 | AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAIoCMMWt5XEuDnOKCg9P2HwAAAAACAAAAAAAQZgAAAAEAACAAAADQWqYgcKq2UtCyC+wiGVtrYgZPX13olXtBQ75U15m5nQAAAAAOgAAAAAIAACAAAADfC3M81L84XMTgfPoXzMu0/77uUdIlEfCbisJZ9RNdMyAAAAAB5ulrzflLQ+O0p3ZnoLDTG+p0NEldVgcghyS3Y1daS0AAAAB3VbUDeJ9DpOacFk8GB/MMMwbsF56EHMdPp+Z4z5jdah4wE+ILqgFIpfeZXxcqjo9I7D32lhqNFvWuQD0QrPrA 11 | 12 | -------------------------------------------------------------------------------- /Properties/PublishProfiles/FolderProfile1.pubxml.user: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | <_PublishTargetUrl>C:\Users\VayneTian\Desktop\resume 10 | AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAbQ7lYW7SGU+OvMyQXDXtvgAAAAACAAAAAAAQZgAAAAEAACAAAAAS+VZcgu8a9AiKdVWbUtP02JVU3sAsC+mlhTekG37KWAAAAAAOgAAAAAIAACAAAACe6zFn+HTOdkm4St1xAlQqUwPYddEMKn6H+tirdfOC/yAAAACFVSENn158KwR5R97fSLvKjHxdMRol4tIp+KBeFFw3wkAAAABh4yatcF7v2XgZETW9/21R3sveQ8paQsK0oNgSEYmDgZFmn6bfGw7/QIp7Q1VBX3S88Qcld7yaCJrm2k6S4Gn2 11 | 12 | -------------------------------------------------------------------------------- /Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to Development environment will display more detailed information about the error that occurred. 20 |

21 |

22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 23 |

24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "name": "asp.net", 4 | "private": true, 5 | "devDependencies": { 6 | "@types/lodash": "4.14.106", 7 | "@types/react": "16.3.10", 8 | "@types/react-bootstrap": "0.32.8", 9 | "@types/react-dom": "16.0.5", 10 | "@types/react-router": "4.0.23", 11 | "@types/react-router-dom": "4.2.6", 12 | "awesome-typescript-loader": "5.0.0", 13 | "clean-webpack-plugin": "0.1.19", 14 | "lodash": "4.17.5", 15 | "react": "16.3.1", 16 | "react-bootstrap": "0.32.1", 17 | "react-dom": "16.3.1", 18 | "react-router": "4.2.0", 19 | "react-router-dom": "4.2.2", 20 | "tslib": "1.9.0", 21 | "typescript": "2.8.1", 22 | "uglifyjs-webpack-plugin": "1.2.4", 23 | "webpack": "4.5.0", 24 | "webpack-cli": "2.0.14", 25 | "json-loader": "0.5.7", 26 | "md5": "2.2.1", 27 | "nebulas": "0.5.0", 28 | "nebpay": "0.0.6", 29 | "jquery": "3.3.1" 30 | }, 31 | "dependencies": {} 32 | } 33 | -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | var CleanWebpackPlugin=require ("clean-webpack-plugin") 2 | import * as Webpack from 'webpack'; 3 | import * as path from "path"; 4 | 5 | var config = { 6 | mode:"production", 7 | module: { 8 | rules: [{ 9 | test: /\.tsx?$/, 10 | use: 'awesome-typescript-loader' 11 | } 12 | ] 13 | }, 14 | node: { 15 | fs: 'empty', 16 | net: 'empty', 17 | tls: 'empty' 18 | }, 19 | 20 | resolve: { 21 | extensions: ['.js', '.ts','.jsx','.tsx'] 22 | }, 23 | entry: ['./App.tsx'], 24 | /*externals: { 25 | 'react': 'React' 26 | },*/ 27 | output: { 28 | path: path.resolve(__dirname, 'wwwroot/'), 29 | // should use absolute path 30 | publicPath: '/', 31 | filename: '../wwwroot/static/main.js' 32 | }, 33 | plugins: [ 34 | // clean wwwroot 35 | new CleanWebpackPlugin([ 36 | 'static/main.js', 37 | ]), 38 | ] 39 | }; 40 | module.exports = config; -------------------------------------------------------------------------------- /Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Package 9 | Release 10 | Any CPU 11 | 12 | True 13 | False 14 | netcoreapp2.0 15 | 2c986c09-f580-4858-8b40-8031b61aa172 16 | C:\Users\VayneTian\Desktop\resumev\ResumeVerification.zip 17 | true 18 | 19 | 20 | -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation", 3 | "homepage": "http://jqueryvalidation.org/", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/jzaefferer/jquery-validation.git" 7 | }, 8 | "authors": [ 9 | "Jörn Zaefferer " 10 | ], 11 | "description": "Form validation made easy", 12 | "main": "dist/jquery.validate.js", 13 | "keywords": [ 14 | "forms", 15 | "validation", 16 | "validate" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "demo", 25 | "lib" 26 | ], 27 | "dependencies": { 28 | "jquery": ">= 1.7.2" 29 | }, 30 | "version": "1.14.0", 31 | "_release": "1.14.0", 32 | "_resolution": { 33 | "type": "version", 34 | "tag": "1.14.0", 35 | "commit": "c1343fb9823392aa9acbe1c3ffd337b8c92fed48" 36 | }, 37 | "_source": "git://github.com/jzaefferer/jquery-validation.git", 38 | "_target": ">=1.8", 39 | "_originalSource": "jquery-validation" 40 | } -------------------------------------------------------------------------------- /Properties/PublishProfiles/FolderProfile2.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | FileSystem 9 | FileSystem 10 | Release 11 | Any CPU 12 | 13 | True 14 | False 15 | netcoreapp2.0 16 | 2c986c09-f580-4858-8b40-8031b61aa172 17 | false 18 | <_IsPortable>true 19 | C:\Users\VayneTian\Desktop\新建文件夹 20 | False 21 | 22 | -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap", 3 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 4 | "keywords": [ 5 | "css", 6 | "js", 7 | "less", 8 | "mobile-first", 9 | "responsive", 10 | "front-end", 11 | "framework", 12 | "web" 13 | ], 14 | "homepage": "http://getbootstrap.com", 15 | "license": "MIT", 16 | "moduleType": "globals", 17 | "main": [ 18 | "less/bootstrap.less", 19 | "dist/js/bootstrap.js" 20 | ], 21 | "ignore": [ 22 | "/.*", 23 | "_config.yml", 24 | "CNAME", 25 | "composer.json", 26 | "CONTRIBUTING.md", 27 | "docs", 28 | "js/tests", 29 | "test-infra" 30 | ], 31 | "dependencies": { 32 | "jquery": "1.9.1 - 3" 33 | }, 34 | "version": "3.3.7", 35 | "_release": "3.3.7", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.3.7", 39 | "commit": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" 40 | }, 41 | "_source": "https://github.com/twbs/bootstrap.git", 42 | "_target": "v3.3.7", 43 | "_originalSource": "bootstrap", 44 | "_direct": true 45 | } -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2016 Twitter, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export class Footer extends React.Component<{}, {}> { //底部 4 | render() { 5 | return
6 |
7 |
8 |
友情链接
9 | 浙江大学| 10 | 浙江大学计算机学院| 11 | 飘渺水云间| 12 | 求是潮| 13 | 缘网| 14 | NexusHD| 15 | 浙江大学广播电视网| 16 | 浙大搜索
17 |
18 | Copyright © Vayne Tian --CC98 --InCAS Lab | Email: vaynetian@cc98.org 19 |
20 |
21 |
22 | } 23 | } -------------------------------------------------------------------------------- /Pages/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /contract.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var ResumeContract = function () { 3 | LocalContractStorage.defineMapProperty(this, "resumedb", { 4 | stringify: function (o) { 5 | return o.toString(); 6 | } 7 | }); 8 | }; 9 | ResumeContract.prototype = { 10 | init: function () { 11 | }, 12 | 13 | save: function (data) { 14 | var from = Blockchain.transaction.from; 15 | var fData = this.resumedb.get(data.nameHash); 16 | if ((fData && fData.owner == from) || fData==null) { 17 | var rData = {"owner":from,"data":data}; 18 | this.resumedb.put(data.nameHash, JSON.stringify(rData)); 19 | }else{ 20 | throw new Error("Cannot update this resume."); 21 | } 22 | }, 23 | query: function (nameHash) { 24 | var from = Blockchain.transaction.from; 25 | var data = this.resumedb.get(nameHash); 26 | 27 | if(data==null){ 28 | throw new Error("No resume before."); 29 | } 30 | if(from==data.owner){ 31 | return data.data; 32 | }else{ 33 | var tData = {nameHash:"",resume:"",resumeHash:data.data.resumeHash}; 34 | return tData; 35 | } 36 | } 37 | }; 38 | module.exports = ResumeContract; -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation-unobtrusive/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation-unobtrusive", 3 | "version": "3.2.6", 4 | "homepage": "https://github.com/aspnet/jquery-validation-unobtrusive", 5 | "description": "Add-on to jQuery Validation to enable unobtrusive validation options in data-* attributes.", 6 | "main": [ 7 | "jquery.validate.unobtrusive.js" 8 | ], 9 | "ignore": [ 10 | "**/.*", 11 | "*.json", 12 | "*.md", 13 | "*.txt", 14 | "gulpfile.js" 15 | ], 16 | "keywords": [ 17 | "jquery", 18 | "asp.net", 19 | "mvc", 20 | "validation", 21 | "unobtrusive" 22 | ], 23 | "authors": [ 24 | "Microsoft" 25 | ], 26 | "license": "http://www.microsoft.com/web/webpi/eula/net_library_eula_enu.htm", 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/aspnet/jquery-validation-unobtrusive.git" 30 | }, 31 | "dependencies": { 32 | "jquery-validation": ">=1.8", 33 | "jquery": ">=1.8" 34 | }, 35 | "_release": "3.2.6", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.2.6", 39 | "commit": "13386cd1b5947d8a5d23a12b531ce3960be1eba7" 40 | }, 41 | "_source": "git://github.com/aspnet/jquery-validation-unobtrusive.git", 42 | "_target": "3.2.6", 43 | "_originalSource": "jquery-validation-unobtrusive" 44 | } -------------------------------------------------------------------------------- /Properties/PublishProfiles/FolderProfile1.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | MSDeploy 9 | Release 10 | Any CPU 11 | http://nebulas.vaynetian.com 12 | True 13 | False 14 | 2c986c09-f580-4858-8b40-8031b61aa172 15 | 111.231.75.113 16 | nebulas 17 | 18 | True 19 | WMSVC 20 | False 21 | administrator 22 | <_SavePWD>True 23 | netcoreapp2.0 24 | false 25 | <_IsPortable>true 26 | 27 | -------------------------------------------------------------------------------- /wwwroot/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 13 | 14 | 15 | 26 | 27 | -------------------------------------------------------------------------------- /Components/Commit.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as Utility from "../Utility"; 3 | import { FormGroup, ControlLabel, FormControl, HelpBlock } from 'react-bootstrap'; 4 | 5 | function FieldGroup({ id, label, ...props }) { 6 | return ( 7 | 8 | {label} 9 | 10 | 11 | ); 12 | } 13 | export class Commit extends React.Component<{}, {}> { 14 | 15 | render() { 16 | return
17 | 23 | 29 | 35 | 41 | 47 | 48 | ; 49 | } 50 | } -------------------------------------------------------------------------------- /wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright jQuery Foundation and other contributors, https://jquery.org/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /Components/Register.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Button, Table, FormControl, FormGroup, ControlLabel, HelpBlock } from 'react-bootstrap'; 3 | 4 | export class Register extends React.Component<{}, { name, password, address }>{ 5 | constructor(props) { 6 | super(props); 7 | this.state = { name: "", password: "", address:"" }; 8 | } 9 | onNameChange(e) { 10 | this.setState({ name: e.target.value }); 11 | } 12 | onPasswdChange(e) { 13 | this.setState({ password: e.target.value }); 14 | } 15 | onAddressChange(e) { 16 | this.setState({ address: e.target.value }); 17 | } 18 | async submit() { 19 | console.log(this.state.name); 20 | console.log(this.state.address); 21 | console.log(this.state.password); 22 | } 23 | FieldGroup({ id, label, help, ...props }) { 24 | return ( 25 | 26 | {label} 27 | 28 | {help && {help}} 29 | 30 | ); 31 | } 32 | render() { 33 | return
34 | {this.FieldGroup({ id: "formControlsText", label: "UserName", type: "text", onChange: this.onNameChange.bind(this), help: null })} 35 | {this.FieldGroup({ id: "formControlsAddress", label: "Address", type: "text", onChange: this.onAddressChange.bind(this), help: null })} 36 | {this.FieldGroup({ id: "formControlsPassword", label: "Password", type: "password", help: null, onChange: this.onPasswdChange.bind(this) })} 37 | 38 |
39 | } 40 | } -------------------------------------------------------------------------------- /Components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { Navbar, Nav, MenuItem, NavItem, NavDropdown } from 'react-bootstrap'; 2 | import * as React from "react"; 3 | export class Header extends React.Component { 4 | render() { 5 | var isLogin = false; 6 | var name = ""; 7 | if (localStorage.getItem("HCAccount")) { 8 | isLogin = true; 9 | name = localStorage.getItem("HCAccount"); 10 | } 11 | let userCenter = null; 12 | if (localStorage.getItem("HCAccount")=="manager") { 13 | userCenter = 14 | 个人中心 15 | ; 16 | } 17 | return
18 | 19 | 20 | 21 | 简历验证平台 22 | 23 | 24 | 25 | 26 | 35 | 36 | 45 | 46 |
; 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { Navbar, Nav, MenuItem, NavItem, NavDropdown,Tab,Tabs } from 'react-bootstrap'; 4 | import { 5 | BrowserRouter as Router, 6 | Route, 7 | Switch 8 | } from 'react-router-dom'; 9 | import { Header } from './Components/Header'; 10 | import { Footer } from './Components/Footer'; 11 | import { MainPage } from './Components/MainPage'; 12 | import { APITest } from './Components/APITest'; 13 | import { HyperChainAPITest } from './Components/HyperChainAPITest'; 14 | import {LogIn} from "./Components/LogIn"; 15 | import { LogOut } from "./Components/LogOut"; 16 | import { MyResume } from "./Components/MyResume"; 17 | import { Veryify } from "./Components/Veryify"; 18 | import { UserCenter } from "./Components/UserCenter"; 19 | import { NebulasTest } from "./Components/NebulasTest"; 20 | import { Register } from "./Components/Register"; 21 | export class App extends React.Component<{}, {}>{ 22 | render() { 23 | return 24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 |
38 | ; 39 | } 40 | } 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ReactDOM.render( 50 | , 51 | document.getElementById('root') 52 | 53 | ); -------------------------------------------------------------------------------- /wwwroot/static/site.css: -------------------------------------------------------------------------------- 1 | .mainpage-body{ 2 | width:100%; 3 | align-items:center; 4 | justify-content:center; 5 | display:flex; 6 | flex-direction:column; 7 | } 8 | .title{ 9 | color:white; 10 | font-size:50px; 11 | margin-top:-260px; 12 | margin-left:80px; 13 | } 14 | .whitelogo { 15 | position: absolute; 16 | margin-left:-650px; 17 | top: 382px; 18 | } 19 | .developer { 20 | color: white; 21 | width: 135px; 22 | position: absolute; 23 | margin-left:-460px; 24 | top: 370px; 25 | font-weight: bolder; 26 | font-size: 18px; 27 | } 28 | .logo { 29 | height: 45px; 30 | position: absolute; 31 | margin-left:-1000px; 32 | top: 380px; 33 | } 34 | .navbar{ 35 | border-radius:0px; 36 | } 37 | .title1 { 38 | color: white; 39 | font-size: 20px; 40 | margin-left: 80px; 41 | } 42 | .mainpage-body li{ 43 | font-size:16px; 44 | } 45 | .root { 46 | display: flex; 47 | flex-direction: column; 48 | } 49 | .root .navbar{ 50 | min-height:60px; 51 | } 52 | .root .navbar-brand{ 53 | line-height:25px; 54 | font-size:18px; 55 | } 56 | .root .container{ 57 | line-height:20px; 58 | } 59 | .root .navbar-nav li a{ 60 | padding-top:17px; 61 | font-size:18px; 62 | } 63 | 64 | .column{ 65 | display:flex; 66 | flex-direction:column; 67 | justify-content:center; 68 | align-items:center; 69 | } 70 | .row{ 71 | display:flex; 72 | justify-content:center; 73 | } 74 | 75 | .footer { 76 | display: flex; 77 | flex-direction: column; 78 | text-align: center; 79 | margin-top: 5rem; 80 | padding-top: 15px; 81 | width: 100%; 82 | min-width: 1140px; 83 | } 84 | 85 | .footerRow { 86 | display: flex; 87 | flex-direction: row; 88 | justify-content: center; 89 | font-size: 12px; 90 | font-family: 微软雅黑; 91 | color: rgb(80,80,80); 92 | margin-bottom: 15px; 93 | } 94 | 95 | .footerRow a { 96 | font-size: 12px; 97 | font-family: 微软雅黑; 98 | color: rgb(80,80,80); 99 | margin-left: 10px; 100 | margin-right: 10px; 101 | } -------------------------------------------------------------------------------- /Components/APITest.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as Utility from "../Utility"; 3 | import {Button } from 'react-bootstrap'; 4 | export class APITest extends React.Component<{}, { token }>{ 5 | constructor(props) { 6 | super(props); 7 | this.state = { token: "" }; 8 | } 9 | 10 | async login() { 11 | const token = await Utility.login("Mana", "org1"); 12 | 13 | this.setState({ token: token }); 14 | } 15 | async createChannel() { 16 | await Utility.createChannel(this.state.token); 17 | } 18 | async joinChannel() { 19 | await Utility.joinChannel(this.state.token); 20 | } 21 | async installChaincode() { 22 | await Utility.installChaincode(this.state.token); 23 | } 24 | async instantiateChaincode() { 25 | await Utility.instantiateChaincode(this.state.token); 26 | } 27 | async invoke() { 28 | await Utility.invoke(this.state.token); 29 | } 30 | async query() { 31 | await Utility.query(this.state.token); 32 | } 33 | render() { 34 | const wellStyles = { maxWidth: 400, margin: '0 auto 10px' }; 35 | 36 | const buttonsInstance = ( 37 |
38 | 41 | 44 | 47 | 50 | 53 | 56 | 59 |
60 | ); 61 | return
62 |
token = {this.state.token}
63 | {buttonsInstance} 64 |
65 | 66 | ; 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /ResumeVerification.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.0 4 | 2.6 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Components/LogIn.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as Hyperchain from "../Hyperchain" 3 | import * as Nebulas from "../Nebulas" 4 | import { Button, Table, FormControl, FormGroup, ControlLabel, HelpBlock} from 'react-bootstrap'; 5 | import { win32 } from 'path'; 6 | export class LogIn extends React.Component<{}, { name, password ,tip}>{ 7 | constructor(props) { 8 | super(props); 9 | this.state = { name: "", password: "" ,tip:""}; 10 | } 11 | handleNameChange(e) { 12 | this.setState({ name: e.target.value }); 13 | } 14 | handlePasswordChange(e) { 15 | this.setState({ password: e.target.value }); 16 | } 17 | async handleLogin() { 18 | const status = await Nebulas.login(this.state.name, this.state.password); 19 | if (status == "success") { 20 | localStorage.setItem("HCAccount", this.state.name); 21 | localStorage.setItem("HCPassword", this.state.password); 22 | this.setState({ tip: "登陆成功" }) 23 | document.location.href = "/"; 24 | } else if (status == "nouser") { 25 | this.setState({ tip: "用户不存在" }) 26 | } else { 27 | this.setState({ tip: "密码错误" }) 28 | } 29 | } 30 | FieldGroup({ id, label, help, ...props }) { 31 | return ( 32 | 33 | {label} 34 | 35 | {help && {help}} 36 | 37 | ); 38 | } 39 | componentDidMount() { 40 | if (localStorage.getItem("HCAccount")) 41 | document.location.href = "/"; 42 | } 43 | render() { 44 | return
45 | {this.FieldGroup({ id: "formControlsText", label: "UserName", type: "text", onChange: this.handleNameChange.bind(this), help: null })} 46 | {this.FieldGroup({ id: "formControlsText", label: "Password", type: "password", onChange: this.handlePasswordChange.bind(this), help: null })} 47 |

{this.state.tip}

48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
管理员帐户名密码
manager123456
64 |
65 |
; 66 | } 67 | } -------------------------------------------------------------------------------- /Pages/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - FabricApplication 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 40 |
41 | @RenderBody() 42 |
43 |
44 |

© 2017 - FabricApplication

45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 66 | 67 | 68 | 69 | @RenderSection("Scripts", required: false) 70 | 71 | 72 | -------------------------------------------------------------------------------- /Components/UserCenter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as Hyperchain from "../Hyperchain" 3 | import * as Nebulas from "../Nebulas"; 4 | import * as $ from 'jquery'; 5 | import { Table, Button } from 'react-bootstrap'; 6 | import { lchmod } from 'fs'; 7 | export class UserCenter extends React.Component<{}, { data }>{ 8 | constructor(props) { 9 | super(props); 10 | this.state = { data: [] }; 11 | 12 | } 13 | async componentDidMount() { 14 | const data = await Nebulas.getReqs(); 15 | this.setState({ data: data }); 16 | } 17 | async accept(item) { 18 | let cid = `#accept_${item.id}`; 19 | let rid = `#reject_${item.id}`; 20 | let tid = `#txtip_${item.id}`; 21 | console.log(item); 22 | const data = JSON.parse(item.resume); 23 | console.log(data); 24 | // await Hyperchain.InvokeContract(Hyperchain.FormData(JSON.parse(Args)), "invoke"); 25 | const args = await Nebulas.addResume(data,item.id); 26 | localStorage.setItem("myResumeState", "accept"); 27 | //localStorage.removeItem("myResumeData"); 28 | $(cid).attr("disabled", true); 29 | $(rid).attr("disabled", true); 30 | $(tid).text("交易已发送,验证交易完成后,您可以去验证简历"); 31 | } 32 | refuse() { 33 | localStorage.setItem("myResumeState", "refuse"); 34 | localStorage.removeItem("myResumeData"); 35 | document.location.href = "/usercenter"; 36 | } 37 | convertReqUI(item) { 38 | console.log(item); 39 | const data = JSON.parse(item.resume); 40 | console.log(data); 41 | const resume =data.resume 42 | console.log(resume); 43 | let cid = `accept_${item.id}`; 44 | let rid = `reject_${item.id}`; 45 | let tid = `txtip_${item.id}`; 46 | return
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
条目内容
姓名{resume.name}
年龄{resume.age}
学历{resume.education}
论文数量{resume.paper}
专利数量{resume.patent}
76 |
77 | 78 | 79 |
80 |
81 |
82 | } 83 | render() { 84 | console.log("我又进来啦"); 85 | if (localStorage.getItem("HCAccount") != "manager") { 86 | return
您没有权限
87 | } 88 | let Args = null; 89 | let UI =

当前没有需要审核的简历

; 90 | let state = localStorage.getItem("myResumeState"); 91 | console.log(state); 92 | 93 | 94 | UI = this.state.data.map(this.convertReqUI.bind(this)); 95 | 96 | return
{UI}
; 97 | } 98 | } -------------------------------------------------------------------------------- /Components/HyperChainAPITest.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as Hyperchain from "../Hyperchain"; 3 | import { Button, Table, Form, FormGroup, Col, ControlLabel, FormControl } from 'react-bootstrap'; 4 | import { lchmod } from 'fs'; 5 | export class HyperChainAPITest extends React.Component<{}, {tip, dat,token ,result}>{ 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | token: "", result: "", tip: "初始化状态", dat: {name:"",age:""}}; 10 | } 11 | 12 | async login() { 13 | const token = await Hyperchain.GetToken(); 14 | this.setState({ token: token,tip:"登陆成功" }); 15 | } 16 | async compileContract() { 17 | await Hyperchain.CompileContract(); 18 | this.setState({ tip: "编译合约成功" }); 19 | } 20 | async deployContract() { 21 | await Hyperchain.DeployContract(); 22 | this.setState({ tip: "部署合约成功" }); 23 | } 24 | async invokeContract() { 25 | const rs = { 26 | name: "吴朝晖", 27 | age: "52", 28 | education: "浙江大学博士", 29 | paper: "180", 30 | patent:"120" 31 | } 32 | const Args = Hyperchain.FormData(["Vayne", "吴朝晖", JSON.stringify(rs)]); 33 | console.log(JSON.stringify(rs)); 34 | await Hyperchain.InvokeContract(Args, "invoke"); 35 | this.setState({ tip: "调用合约成功" }); 36 | } 37 | async queryContract() { 38 | const Args = ["Vayne", this.state.dat.name]; 39 | console.log(Args); 40 | const data =await Hyperchain.InvokeContract(Args, "query"); 41 | const ret:string = data.Ret; 42 | var r = ret.substring(2); 43 | const buf = new Buffer(r, 'hex'); 44 | const s = buf.toString(); 45 | this.setState({ result: s, tip: "查询成功 参数为" + Args.toString() }); 46 | } 47 | async queryMyself() { 48 | const Args = ["Mana", "Mana"]; 49 | const data = await Hyperchain.InvokeContract(Args, "query"); 50 | const ret: string = data.Ret; 51 | var r = ret.substring(2); 52 | const buf = new Buffer(r, 'hex'); 53 | const s = buf.toString(); 54 | this.setState({ result: s, tip: "查询成功 参数为" + Args.toString() }); 55 | } 56 | 57 | handleNameChange(e) { 58 | var t = { name: e.target.value, age: this.state.dat.age } 59 | this.setState({ dat: t }); 60 | } 61 | handleAgeChange(e) { 62 | var t = { name: this.state.dat.name, age: e.target.value } 63 | this.setState({ dat: t }); 64 | } 65 | render() { 66 | const wellStyles = { maxWidth: 400, margin: '0 auto 10px' }; 67 | 68 | const buttonsInstance = ( 69 |
70 | 73 | 76 | 79 | 82 | 85 | 88 | 89 |
90 | ); 91 | return
92 |
{buttonsInstance}
93 |
94 |
95 | 96 | 97 |
98 | 99 | 100 | 101 | 102 | 103 |
104 |
105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
条目内容
Token{this.state.token}
Result{this.state.result}
State{this.state.tip}
126 |
127 | 128 | ; 129 | } 130 | 131 | } -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** Unobtrusive validation support library for jQuery and jQuery Validate 3 | ** Copyright (C) Microsoft Corporation. All rights reserved. 4 | */ 5 | !function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a("
  • ").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function m(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=p.unobtrusive.options||{},m=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),m("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),m("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),m("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var u,p=a.validator,v="unobtrusiveValidation";p.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=m(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){p.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=m(this);a&&a.attachValidation()})}},u=p.unobtrusive.adapters,u.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},u.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},u.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},u.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},p.addMethod("__dummy__",function(a,e,n){return!0}),p.addMethod("regex",function(a,e,n){var t;return this.optional(e)?!0:(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),p.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),p.methods.extension?(u.addSingleVal("accept","mimtype"),u.addSingleVal("extension","extension")):u.addSingleVal("extension","extension","accept"),u.addSingleVal("regex","pattern"),u.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),u.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),u.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),u.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),u.add("required",function(a){("INPUT"!==a.element.tagName.toUpperCase()||"CHECKBOX"!==a.element.type.toUpperCase())&&e(a,"required",!0)}),u.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),u.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),a(function(){p.unobtrusive.parse(document)})}(jQuery); -------------------------------------------------------------------------------- /Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model IndexModel 3 | @{ 4 | ViewData["Title"] = "Home page"; 5 | } 6 | 7 | 69 | 70 | 109 | -------------------------------------------------------------------------------- /Components/MainPage.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs,Tab,Table,ListGroup,ListGroupItem } from 'react-bootstrap'; 2 | import * as React from "react"; 3 | import * as NebPay from "nebpay"; 4 | export class MainPage extends React.Component<{}, { key }> { 5 | constructor(props, context) { 6 | super(props, context); 7 | 8 | this.handleSelect = this.handleSelect.bind(this); 9 | 10 | this.state = { 11 | key: 1 12 | }; 13 | } 14 | 15 | handleSelect(key) { 16 | this.setState({ key }); 17 | } 18 | 19 | render() { 20 | var nebPay = new NebPay; 21 | console.log(nebPay); 22 | console.log(nebPay.call); 23 | return
    24 | 25 |
    26 | 27 |

    基于星云链的简历验证系统

    28 |

    致力于在去中心化的环境下为企业提供一个安全可信的简历验证解决方案 29 |

    30 |
    31 |
    32 |
    33 | 34 | 35 | 测试教程&Github地址: https://github.com/Dearkano/ResumeVerification 36 | 本系统属于个人开发原创,如有任何抄袭行为将予以追究 37 | 开发者:Vayne Tian 浙江大学计算机学院InCAS实验室 38 | 39 | 40 |
    41 | 42 |
    43 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
    条目内容
    姓名比尔·盖茨
    年龄63
    学历哈佛大学肄业
    论文数量1
    专利数量3000/年
    79 |
    80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 |
    条目内容
    姓名御坂美琴
    年龄16
    学历常盘台中学
    论文数量0
    专利数量1(超电磁炮)
    111 |
    112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 |
    条目内容
    姓名吴朝晖
    年龄52
    学历浙江大学博士
    论文数量180
    专利数量120
    143 |
    144 |
    145 |
    146 |
    147 |
    ; 148 | } 149 | } -------------------------------------------------------------------------------- /Nebulas.tsx: -------------------------------------------------------------------------------- 1 | import * as md5 from "md5"; 2 | import * as NebPay from "nebpay"; 3 | import * as $ from 'jquery'; 4 | export function formData(Args) { 5 | //生成md5 6 | var resume = Args[2]; 7 | var address = Args[0]; 8 | var resumeHash = md5(resume.toString()); 9 | console.log("on chain = " + resume); 10 | console.log("on chain hash = " + resumeHash); 11 | var nameHash = md5(Args[1]); 12 | var args = [address, nameHash, resume, resumeHash]; 13 | return args; 14 | } 15 | 16 | function getContractAddress() { 17 | return "n1rkPbRrsvesLJFS8HqUBmXE3ZrxSCLqGih"; 18 | } 19 | 20 | export async function getNonce(address) { 21 | const url = "https://mainnet.nebulas.io/v1/user/accountstate"; 22 | const headers = { "Content-Type": "application/json" }; 23 | const body = JSON.stringify({ "address": address }); 24 | const response = await fetch(url, { method: "POST", headers, body }); 25 | const data = await response.json(); 26 | const nonce = data.result.nonce; 27 | return nonce; 28 | } 29 | var intervalQuery; 30 | async function updateReq(id) { 31 | const url = `http://111.231.75.113:9012/api/resume/update/${id}`; 32 | await fetch(url); 33 | } 34 | export async function addResume(args,id) { 35 | var nebPay = new NebPay(); 36 | var serialNumber; //交易序列号 37 | var intervalQuery; //定时查询交易结果 38 | var to = "n1rkPbRrsvesLJFS8HqUBmXE3ZrxSCLqGih"; //Dapp的合约地址 39 | var value = "0"; 40 | var callFunction = "save" //调用的函数名称 41 | const data = [{ nameHash: args.name, resume: JSON.stringify(args.resume), resumeHash: args.nameHash }]; 42 | var callArgs = JSON.stringify(data); //参数格式为参数数组的JSON字符串, 比如'["arg"]','["arg1","arg2]' 43 | 44 | 45 | //发送交易(发起智能合约调用) 46 | serialNumber = nebPay.call(to, value, callFunction, callArgs); 47 | await updateReq(id); 48 | //设置定时查询交易结果 49 | intervalQuery = setInterval(function () { 50 | funcIntervalQuery(serialNumber,null); 51 | }, 10000); //建议查询频率10-15s,因为星云链出块时间为15s,并且查询服务器限制每分钟最多查询10次。 52 | 53 | 54 | //const url = "http://111.231.75.113:8685/v1/admin/sign "; 55 | //const headers = { "Content-Type": "application/json" }; 56 | //let nonce = await getNonce(args[0]); 57 | //const c = 1; 58 | //console.log(nonce); 59 | //nonce = parseInt(nonce) + c; 60 | //console.log(nonce); 61 | //const data = [{ nameHash: args[1], resume: args[2], resumeHash: args[3] }]; 62 | //const body = JSON.stringify({ 63 | // transaction: { 64 | // from: args[0], 65 | // to: getContractAddress(), 66 | // value: "0", 67 | // nonce: nonce, 68 | // gasPrice: "1000000", 69 | // gasLimit: "2000000", 70 | // contract: { "function": "save", "args": JSON.stringify(data) } 71 | // }, 72 | // passphrase: localStorage.getItem("AccountSecret") 73 | //}); 74 | //const response = await fetch(url, { method: "POST", headers, body }); 75 | //const rs = await response.json(); 76 | 77 | //console.log(rs.result); 78 | //const abi = rs.result.data; 79 | //const _url = "https://mainnet.nebulas.io/v1/user/rawtransaction"; 80 | //const body1 = JSON.stringify({ data: abi }); 81 | //const _response = await fetch(_url, { method: "POST", headers, body:body1 }); 82 | } 83 | function funcIntervalQuery(serialNumber, options) { 84 | var nebPay = new NebPay(); 85 | //queryPayInfo的options参数用来指定查询交易的服务器地址,(如果是主网可以忽略,因为默认服务器是在主网查询) 86 | nebPay.queryPayInfo(serialNumber, options) //search transaction result from server (result upload to server by app) 87 | .then(function (resp) { 88 | console.log("tx result: " + resp) //resp is a JSON string 89 | var respObject = JSON.parse(resp) 90 | //code==0交易发送成功, status==1交易已被打包上链 91 | if (respObject.code === 0 && respObject.data.status === 1) { 92 | //交易成功,处理后续任务.... 93 | $("#txtip").text("交易已完成,您现在可以去验证简历"); 94 | document.location.href = "/usercenter"; 95 | 96 | clearInterval(intervalQuery) //清除定时查询 97 | } 98 | }) 99 | .catch(function (err) { 100 | console.log(err); 101 | }); 102 | } 103 | 104 | export async function queryResume(nameHash) { 105 | const url = "https://mainnet.nebulas.io/v1/user/call"; 106 | const headers = { "Content-Type": "application/json" }; 107 | const nh = JSON.stringify([nameHash]); 108 | const account = localStorage.getItem("HCAccount"); 109 | const body = JSON.stringify( 110 | { 111 | "from": "n1bz1jRkWC37Ka2xZY3zQhhLn3C5PfYpG9p", 112 | "to": getContractAddress(), 113 | "value": "0", 114 | "nonce": 0, 115 | "gasPrice": "1000000", 116 | "gasLimit": "2000000", 117 | "contract": { 118 | "function": "query", "args": nh 119 | } 120 | }); 121 | const response = await fetch(url, { method: "POST", headers, body }); 122 | const data = await response.json(); 123 | return data; 124 | } 125 | 126 | 127 | export async function login(name, password) { 128 | const url = `http://111.231.75.113:9012/api/account/login`; 129 | const headers = { "Content-Type": "application/json" }; 130 | const body = JSON.stringify( 131 | { 132 | name: name, 133 | password: password, 134 | address: "" 135 | }) 136 | const response = await fetch(url, { method: "POST", headers, body }); 137 | if (response.status == 200) { 138 | return "success"; 139 | } else if (response.status == 401) { 140 | const data = await response.text(); 141 | if (data == "nouser") { 142 | return "nouser"; 143 | } else { 144 | return "passwordError" 145 | } 146 | } 147 | } 148 | 149 | export async function handoutReq(name, data) { 150 | const url = `http://111.231.75.113:9012/api/resume/request`; 151 | const headers = { "Content-Type": "application/json" }; 152 | const body = JSON.stringify( 153 | { 154 | name: name, 155 | resume: data 156 | }) 157 | const response = await fetch(url, { method: "POST", headers, body }); 158 | } 159 | 160 | export async function getReqs() { 161 | const url = `http://111.231.75.113:9012/api/resume/request`; 162 | 163 | const response = await fetch(url); 164 | const data = response.json(); 165 | return data; 166 | } -------------------------------------------------------------------------------- /Hyperchain.tsx: -------------------------------------------------------------------------------- 1 | import * as md5 from "md5"; 2 | var request = require("request"); 3 | async function test() { 4 | //curl("https://api.hyperchain.cn/v1/token/gtoken", {}) 5 | } 6 | async function HCFetch(_url, jsonStr) { 7 | const token = localStorage.getItem("token"); 8 | const headers = { "Authorization": token, "Content-Type": "application/json", "Accept": "application/json", "Origin": "chrome-extension://eajclkbhbmkkkpbdedfhmeffeobjkgid" }; 9 | const url = `https://api.hyperchain.cn/v1/` + _url; 10 | const response = await fetch(url, { method: "POST", headers, body: jsonStr }); 11 | const data = await response.json(); 12 | return data; 13 | /* request({ 14 | url: url, 15 | method: "POST", 16 | headers: headers, 17 | body: jsonStr 18 | }, function (error, response, body1) { 19 | if (!error && response.statusCode == 200) { 20 | var body2 = JSON.parse(body1); 21 | var rs = body2.Cts; 22 | console.log(rs[0]); 23 | return rs[0]; 24 | } 25 | }); 26 | return null;*/ 27 | } 28 | 29 | export async function GetToken() { 30 | const url = `https://api.hyperchain.cn/v1/token/gtoken`; 31 | const headers = { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json" }; 32 | const body = "phone=18868104684&password=tzj13999567168&client_id=c18f2ead-eeb8-4c0b-8378-b6c2ce129f57&client_secret=1108M45t16X2F399706f9p12cv10Pq3H"; 33 | const response = await fetch(url, { method: "POST", headers, body }); 34 | const data = await response.json(); 35 | const token = data.access_token; 36 | console.log(token); 37 | localStorage.setItem("token", token); 38 | return token; 39 | /*request({ 40 | url: url, 41 | method: "POST", 42 | headers: headers, 43 | body:body 44 | }, function (error, response, body1) { 45 | if (!error && response.statusCode == 200) { 46 | console.log(body1); 47 | var body2 = JSON.parse(body1); 48 | token = body2.access_token; 49 | console.log(token); 50 | localStorage.setItem("token", token); 51 | } 52 | }); */ 53 | 54 | 55 | /* const option = { 56 | json: true, 57 | header: headers, 58 | body: body 59 | }; 60 | request.post(url, option, function (error, response, body) { 61 | console.log(body); 62 | }); 63 | */ 64 | } 65 | function resultFunction(callback, error, response, body) { 66 | if (!error && response.statusCode === 200) { 67 | callback({ success: true, msg: body }); 68 | console.log('request is success '); 69 | } else { 70 | console.log('request is error', error); 71 | callback({ success: false, msg: error }); 72 | } 73 | } 74 | export async function CompileContract() { 75 | const contract = "contract Resume { struct Rsm{ bytes32 name; string val; string valhash; } mapping(bytes32=>Rsm) public data; function invoke(bytes32 userName, bytes32 _name,string _val,string _valhash){ Rsm resume ; resume.name=_name; resume.val=_val; resume.valhash=_valhash; data[_name]=resume; } function query(bytes32 userName,bytes32 name)returns(string){ if(name==userName){ return data[name].val; }else{ return data[name].valhash; } } } "; 76 | const url = "dev/contract/compile"; 77 | const body = { "CTCode": contract }; 78 | const data = await HCFetch(url, JSON.stringify(body)); 79 | const CTS = data.Cts[0]; 80 | const Abi = CTS.Abi; 81 | const Bin = CTS.Bin; 82 | localStorage.setItem("Abi", Abi); 83 | localStorage.setItem("Bin", Bin); 84 | } 85 | 86 | export async function DeployContract() { 87 | const addr = "be0d7d79dbd922bebc4aab63045ebee529f18395"; 88 | localStorage.setItem("address", addr); 89 | const Bin = localStorage.getItem("Bin"); 90 | const body = { "Bin": Bin, "From": addr }; 91 | const url = "dev/contract/deploysync"; 92 | const data = await HCFetch(url, JSON.stringify(body)); 93 | const contractAddr = data.ContractAddress; 94 | localStorage.setItem("contractAddress", contractAddr); 95 | } 96 | 97 | export async function GetPayload(Args, func) { 98 | const Abi = localStorage.getItem("Abi"); 99 | const body = { "Abi": Abi, "Args": Args, "Func": func }; 100 | const url = "dev/payload"; 101 | const data = await HCFetch(url, JSON.stringify(body)); 102 | return data; 103 | } 104 | export async function InvokeContract(Args, func) { 105 | const url = "dev/contract/invokesync"; 106 | const PayLoad = await GetPayload(Args, func); 107 | const From = localStorage.getItem("address"); 108 | const To = localStorage.getItem("contractAddress"); 109 | const body = { "Const": false, "PayLoad": PayLoad, "From": From, "To": To }; 110 | const data = await HCFetch(url, JSON.stringify(body)); 111 | return data; 112 | } 113 | 114 | export function FormData(Args: string[]) { 115 | //生成md5 116 | var str = Args[2].toString(); 117 | console.log("formdata str=" + str); 118 | var hash = md5(str); 119 | console.log("zij=" + hash); 120 | Args.push(hash); 121 | console.log(Args); 122 | return Args; 123 | } 124 | 125 | export function Account(name, psw) { 126 | var accounts = [ 127 | { name: "Vayne", password: "vayne" }, 128 | { name: "Mana", password: "mana" }, 129 | { name: "HR", password: "hr" }, 130 | { name: "CA", password: "ca" }, 131 | { name: "Myrcella", password: "myrcella" } 132 | ]; 133 | for (var ac of accounts) { 134 | if (name === ac.name) { 135 | if (psw === ac.password) { 136 | if (name === 'Vayne') { 137 | var data = { 138 | name: "吴朝晖", 139 | age: 52, 140 | education: "浙江大学博士", 141 | paper: 180, 142 | patent: 120 143 | } 144 | localStorage.setItem("myResume", JSON.stringify(data)); 145 | } else if (name === "Mana" && localStorage.getItem("seeacc") === "true") { 146 | localStorage.setItem("seeacc", "false"); 147 | } 148 | else if (name === "Mana") { 149 | localStorage.removeItem("myResume"); 150 | localStorage.setItem("seeacc", "true"); 151 | } 152 | return true; 153 | } 154 | } 155 | } 156 | return false; 157 | } -------------------------------------------------------------------------------- /Utility.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * { 3 | "success": true, 4 | "secret": "RaxhMgevgJcm", 5 | "message": "Jim enrolled Successfully", 6 | "token": "" 7 | } 8 | * @param name 9 | * @param org 10 | */ 11 | export async function login(name,org) { 12 | const url = `http://47.100.192.19:4010/users`; 13 | const headers = { 14 | "Content-Type": "application/x-www-form-urlencoded" 15 | }; 16 | const body = 'username='+name+'&orgName='+org; 17 | const response = await fetch(url, { method: "POST", headers, body }); 18 | const data = await response.json(); 19 | return data.token; 20 | } 21 | export function trim(str) { 22 | return str.replace(/\s|\xA0|\x00/g, ""); 23 | } 24 | export function outASCii(str) { 25 | var len = 0; 26 | for (var i = 0; i < str.length; i++) { 27 | var c = str.charCodeAt(i); 28 | console.log(parseInt(c)); 29 | } 30 | } 31 | export function strlen(str) { 32 | var len = 0; 33 | for (var i = 0; i < str.length; i++) { 34 | var c = str.charCodeAt(i); 35 | //单字节加1 36 | if ((c >= 0x0001 && c <= 0x007e) || (0xff60 <= c && c <= 0xff9f)) { 37 | len++; 38 | } 39 | else { 40 | len += 2; 41 | } 42 | } 43 | return len; 44 | } 45 | export async function createChannel(token) { 46 | const url = 'http://47.100.192.19:4010/channels'; 47 | const headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" }; 48 | const body = { 49 | "channelName": "mychannel", 50 | "channelConfigPath": "../artifacts/channel/mychannel.tx" 51 | }; 52 | const response = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) }); 53 | return await response.json(); 54 | } 55 | 56 | 57 | export async function joinChannel(token) { 58 | const url = 'http://47.100.192.19:4010/channels/mychannel/peers'; 59 | const headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" }; 60 | const body = { 61 | "peers": [ 62 | "peer0.org1.example.com", 63 | "peer1.org1.example.com", 64 | 65 | ] 66 | }; 67 | const response = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) }); 68 | return await response.json(); 69 | } 70 | 71 | 72 | export async function installChaincode(token) { 73 | const url = 'http://47.100.192.19:4010/chaincodes'; 74 | const headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" }; 75 | const body = { 76 | "peers": ["peer0.org1.example.com", "peer1.org1.example.com" ,], 77 | "chaincodeName": "mycc", 78 | "chaincodePath": "github.com/example_cc/go", 79 | "chaincodeType": "golang", 80 | "chaincodeVersion": "v0" 81 | }; 82 | const response = await fetch(url, { method: "POST", headers, body:JSON.stringify(body) }); 83 | return await response.json(); 84 | } 85 | 86 | 87 | export async function instantiateChaincode(token) { 88 | const url = 'http://47.100.192.19:4010/channels/mychannel/chaincodes'; 89 | const headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" }; 90 | const body = { 91 | "peers": ["peer0.org1.example.com", "peer1.org1.example.com" ], 92 | "chaincodeName": "mycc", 93 | "chaincodeVersion": "v0", 94 | "chaincodeType": "golang", 95 | "args": ["a", "100", "b", "200"] 96 | }; 97 | const response = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) }); 98 | return await response.json(); 99 | } 100 | 101 | export async function invoke(token) { 102 | const url = 'http://47.100.192.19:4010/channels/mychannel/chaincodes/mycc'; 103 | const headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" }; 104 | const body = { 105 | "peers": ["peer0.org1.example.com", "peer1.org1.example.com" ], 106 | "fcn": "move", 107 | "args": ["a", "b", "10"] 108 | }; 109 | const response = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) }); 110 | return await response.json(); 111 | } 112 | 113 | export async function query(token) { 114 | const url = 'http://47.100.192.19:4010/channels/mychannel/chaincodes/mycc?peer=peer0.org1.example.com&fcn=query&args=%5B%22a%22%5D'; 115 | const headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" }; 116 | const response = await fetch(url, {headers}); 117 | return await response.json(); 118 | } 119 | 120 | export async function queryBlock(token,id) { 121 | const url = 'http://47.100.192.19:4010/channels/mychannel/blocks/'+id+'?peer=peer0.org1.example.com'; 122 | const headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" }; 123 | const response = await fetch(url, { headers }); 124 | return await response.json(); 125 | } 126 | 127 | export async function queryTraction(token, id) { 128 | const url = 'http://47.100.192.19:4010/channels/mychannel/transactions/' + id + '?peer=peer0.org1.example.com'; 129 | const headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" }; 130 | const response = await fetch(url, { headers }); 131 | return await response.json(); 132 | } 133 | 134 | export async function queryChainInfo(token) { 135 | const url = 'http://47.100.192.19:4010/channels/mychannel?peer=peer0.org1.example.com'; 136 | const headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" }; 137 | const response = await fetch(url, { headers }); 138 | return await response.json(); 139 | } 140 | 141 | export async function queryInstalledChaincode(token) { 142 | const url = "http://47.100.192.19:4010/chaincodes?peer=peer0.org1.example.com&type=installed"; 143 | const headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" }; 144 | const response = await fetch(url, { headers }); 145 | return await response.json(); 146 | } 147 | 148 | export async function queryInstantiatedChaincode(token) { 149 | const url = "http://47.100.192.19:4010/chaincodes?peer=peer0.org1.example.com&type=instantiated"; 150 | const headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" }; 151 | const response = await fetch(url, { headers }); 152 | return await response.json(); 153 | } 154 | 155 | export async function queryChannels(token) { 156 | const url = "http://47.100.192.19:4010/channels?peer=peer0.org1.example.com"; 157 | const headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" }; 158 | const response = await fetch(url, { headers }); 159 | return await response.json(); 160 | } 161 | -------------------------------------------------------------------------------- /Components/MyResume.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as Hyperchain from "../Hyperchain" 3 | import * as Nebulas from "../Nebulas" 4 | import * as md5 from 'md5'; 5 | import { Table, Button, ProgressBar } from 'react-bootstrap'; 6 | import { NEG_ONE } from 'long'; 7 | interface Resume { 8 | name: string, 9 | age: string, 10 | education: string, 11 | paper: string, 12 | patent: string 13 | } 14 | export class MyResume extends React.Component<{}, { resume: Resume }>{ 15 | constructor(Props) { 16 | super(Props); 17 | this.state = { 18 | resume: { name: "", age: "", education: "", paper: "", patent: "" } 19 | } 20 | } 21 | async createResume() { 22 | const jstr = JSON.stringify(this.state.resume); 23 | localStorage.setItem("myResume", jstr); 24 | const curUser = localStorage.getItem("HCAccount"); 25 | const resumeHash = md5(jstr); 26 | const nameHash = md5(this.state.resume.name); 27 | let Args = { 28 | resume: this.state.resume, 29 | name: nameHash, 30 | resumeHash:resumeHash 31 | }; 32 | localStorage.setItem("myResumeData", JSON.stringify(Args)); 33 | localStorage.setItem("myResumeState", "wait"); 34 | await Nebulas.handoutReq(curUser, JSON.stringify(Args)); 35 | document.location.href = "/myresume"; 36 | } 37 | handleNameChange(e) { 38 | var t = this.state.resume; 39 | t.name = e.target.value; 40 | this.setState({ resume: t }); 41 | } 42 | handleAgeChange(e) { 43 | var t = this.state.resume; 44 | t.age = e.target.value; 45 | this.setState({ resume: t }); 46 | } 47 | handleEducationChange(e) { 48 | var t = this.state.resume; 49 | t.education = e.target.value; 50 | this.setState({ resume: t }); 51 | } 52 | handlePaperChange(e) { 53 | var t = this.state.resume; 54 | t.paper = e.target.value; 55 | this.setState({ resume: t }); 56 | } 57 | handlePatentChange(e) { 58 | var t = this.state.resume; 59 | t.patent = e.target.value; 60 | this.setState({ resume: t }); 61 | } 62 | nextResume() { 63 | localStorage.removeItem("myResume"); 64 | localStorage.removeItem("myResumeData"); 65 | document.location.href = "/myresume"; 66 | } 67 | render() { 68 | if (!localStorage.getItem("HCAccount")) { 69 | return

    您还未登录 请先登陆

    70 | } 71 | if (!localStorage.getItem("myResume")) { 72 | return
    75 |

    您还没有属于自己的简历,先创建一份吧^_^

    76 |
    77 |
    78 | 79 | 80 |
    81 |
    82 | 83 | 84 |
    85 |
    86 | 87 | 88 |
    89 |
    90 | 91 | 92 |
    93 |
    94 | 95 | 96 |
    97 | 98 | 99 |
    100 | 101 |
    ; 102 | } else { 103 | let tip = ""; 104 | let progress = null; 105 | if (localStorage.getItem("myResumeState") == "accept") { 106 | tip = "审核通过"; 107 | progress = ; 108 | } 109 | else if (localStorage.getItem("myResumeState") == "refuse") { 110 | tip = "审核未通过"; 111 | progress = ; 112 | } 113 | else if (localStorage.getItem("myResumeState") == "wait") { 114 | tip = "审核中"; 115 | progress = ; 116 | } 117 | 118 | const Args = JSON.parse(localStorage.getItem("myResumeData")); 119 | console.log(Args); 120 | const nameHash = Args.name; 121 | //const address = Args[0]; 122 | const data = Args.resume; 123 | return
    126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |
    条目内容
    姓名{data.name}
    姓名哈希{nameHash}
    地址qdw
    年龄{data.age}
    学历{data.education}
    论文数量{data.paper}
    专利数量{data.patent}
    164 | {progress} 165 |
    {tip}
    166 | 167 |
    ; 168 | } 169 | 170 | } 171 | } -------------------------------------------------------------------------------- /wwwroot/images/banner2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于星云链的简历验证系统 2 | 3 | Nebulas新App奖 https://incentive.nebulas.io/dapps-board.html#5 4 | 5 | **Abstract** 6 | 7 | With the rapid development of information technology and computer science, 8 | blockchain has become an important role in various areas, especially cases like 9 | verification or transaction in which trust is mostly needed. As a advanced 10 | technology in blockchain service, permissioned blockchain can be very helpful in 11 | resume verification to increase HR's efficiency. 12 | 13 | **关键字**: 简历验证 分布式 区块链 14 | 15 | # 0. 测试系统 16 | 17 | ## step1 18 | 19 | 打开demo网页 http://nebulas.vaynetian.com 20 | 21 | * 1.在右上角点击注册 22 | * 2.填写用户名,密码,星云地址 23 | * 3.点击注册 24 | * 4.在右上角点击登陆 25 | * 5.登陆刚才注册的账号 26 | 27 | ## step2 28 | * 1.在左上角点击“我的简历” 29 | * 2.输入一份简历demo 30 | * 3.点击提交 31 | * 4.可以看到待审核字样 32 | 33 | ## step3 34 | * 1.在右上角注销 35 | * 2.使用下面的管理员账号登陆(账号:manager 密码:123456) 36 | * 3.点击右上角“个人中心” 37 | * 4.可以看到刚才user提交的简历,点击“通过”,通过插件完成交易,这时会对公链进行上链操作 38 | * 5.由于DPOS的共识,等待15s左右 39 | 40 | ## step4 41 | * 1.点击验证简历 42 | * 2.在step1下输入刚才简历中的名字,可以查询到简历哈希 43 | * 3.在step2输入刚才的简历内容(在我的简历中可以查看,此处为方便展示管理员与user使用同一份“我的简历”“),点击验证,可以发现hash相同,验证成功 44 | * 4.在step2输入简历,但可以修改其中一点内容,再进行验证,发现验证失败 45 | * 5.在step3(模拟HR实际工作场景)中,点击“获取简历”,从json文件中读取2份简历(预先已经上链),点击验证,可以发现一份成功,另一份失败 46 | 47 | # 1. 总览 Introduction 48 | 49 | 50 | 无论对于求职者还是企业来说,招聘都是一件很重要的事。刚刚走出大学校园的毕业生,满怀希望地将自己的简历发到HR的邮箱期待获得进一步面试的机会;HR看着邮箱中上百份的简历,每份简历中都有求职者各式各样的经历和成就,要么一眼扫过不加辨别,要么对其中的不同信息一一甄别。最后,不是求职者的机会减少,就是HR的效率降低,导致筛查时间变长,依然损伤了求职者的利益。 51 | 52 | 具体数据分析将在2.2节中详细阐述。 53 | 54 | 为了解决这一难题,一方面提高HR的工作效率,为企业高效地筛选所需人才,另一方面增加求职者的机会,使他们的简历可以被更认真的对待,我们提出了基于联盟链技术的简历验证系统。这一多中心化的系统将安全地储存求职者简历中的客观信息,使得HR可以利用计算机技术迅速的筛查简历中各种客观信息(成绩、获奖、论文、专利、实习等)的真实性,更注重和专心于求职者的主观表述,从而科学严谨地进行筛选。 55 | 56 | 这一系统的技术细节将在第3节中详细阐述。 57 | 58 | 区块链是一种去中心化的数据库技术,拥有对数据的可追溯性,数据的不可篡改性。因此在很多领域拥有广泛的前景并获得认可。 59 | 60 | 区块链技术背景将在2.3节中阐述。 61 | 62 | 区块链技术近年来发展极快,拥有强大的社区生态以及企业联盟。其开源开放的特性使得技术发展迅速,2009年中本聪的比特币系统[1],区块链1.0时代开始。不久,代表着区块链2.0的拥有智能合约的以太坊社区出现[2]。IBM牵头的linux基金会企业的HyperLedger联盟链项目更是在商业化领域很快崭露头角。因此在不同的服务场景下,选用何种区块链技术也是一个需要仔细抉择的问题。 63 | 64 | 不同区块链技术的背景和特性将在第4节中讨论。 65 | 66 | # 2. 背景 Background 67 | 68 | 69 | ### 2.1 问题的提出 70 | 71 | HR对一个职位的应聘简历进行筛选时,会有自己独特的一些方法,但总绕不开对硬性(客观)条件的筛选判定。如果一位求职者声称自己毕业于浙江大学,有三项专利,HR如何迅速确定这份信息是真实的呢?诚然,在只有一份简历的情况下,HR可以去浙江大学的数据库去查有无此人,可以去国家专利库查询专利号,但是事实上,面对着大量的上百份的简历,HR对一份简历可以分配的时间可能只有不到一分钟,如果每一份简历都需要对硬性条件进行核查,将会极大降低效率。 72 | 73 | 当然,HR也可以假定这个人没有在简历上造假或是决定在面试时进行核查,于是给予了这位求职者面试机会。然而在面试时却发现求职者毕业于浙江大学城市学院,三项专利并不属于国家专利,于是不予录用,并且浪费了面试以及前前后后所需要的准备工作所花费的时间。 74 | 75 | 因此,为HR在这样的场景下可以提供怎样一个适当的解决方案呢? 76 | 77 | ### 2.2 需求分析 78 | 79 | 李开复说过:“如果是好的公司,一定会去确认履历里面的内容的。在我的工作经验中,曾经有一个人面试一个总经理的职位,结果很好,可是后来发现他的学历是假的,结果当然没雇。在好的公司,我们很高兴那些没有诚信的人在履历里作假,因为那增加了我们在雇用时抓到他的机会。建议你在履历之外,写一封热情洋溢,积极主动,充分描述你的专长和和优势,还有为什么你适合那个公司的信(cover 80 | letter)。不要只谈些泛泛的话,要:(1)证明你花了时间去理解每一个申请对象,(2)用实例说明和证明你的专长、优势、诚意。” 81 | 82 | 可见,一份简历的真实性对于HR,对于公司都是十分重要的。 83 | 84 | 应届生论坛上的一份调查显示,有34%的参与者认为自己“简历很真实,绝对没有掺假”。而有超过10%的参与者承认自己简历造假,近50%的参与者承认夸大了自己的经历,只有约5%的参与者认为“造假迟早会被打回原形”。[3] 85 | 86 | 同时,他人简历造假行为与个人简历造假行为呈正相关关系[4],也就是当简历造假之势愈演愈烈之时,很可能会形成潜规则或是风气。 87 | 88 | 从个人对简历造假的态度方面看,21. 4%认为简历造假是有必 要的;37. 89 | 5%的人认为简历适当修饰可以理解,表明有近六成人对简历修饰和造假表示认同。仅28. 90 | 3%的人鄙视简历造假。可见,人们对求职过程中的简历造假态度过于宽容,简历真实程度过低。此外,仅有17. 91 | 2%的被访者认为求职简历中作假不利于个人的求职,其余或多或少会认为简历作假是有利的。可见,社会上大部分人对简历作假存在一定的认识误区。[5] 92 | 93 | 从以上数据可以得知,简历注水造假是实际存在的严重问题。 94 | 95 | ### 2.3 区块链技术 96 | 97 | 区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。本质上是一个去中心化的数据库。 98 | 区块链是一串使用密码学方法相关联产生的数据块,每一个数据块中包含了交易的信息,用于验证其信息的有效性(防伪)和生成下一个区块。 99 | 100 | 101 | # 3. 实现 Implement 102 | 103 | 104 | ![](media/314a07267ced6fd8f8cf8106d4015dca.png) 105 | 106 | ### 3.1 流程 107 | 108 | 简历验证系统将分为两套操作流程:求职者的简历上传流程和HR的审核流程。 109 | 110 | **3.1.1 求职者流程** 111 | 112 | 对于求职者来说,要做的事情其实和以前不会有非常大的区别,便利将是本系统的优势之一。 113 | 114 | 为了保证简历的真实性,求职者将不会拥有直接将信息上链的权限。这也是为何会使用拥有准入机制的联盟链技术而不是现有的公链,例如以太坊。只有CA机构将拥有对账本直接交互的权力,这在一定程度上与区块链去中心化的初衷相违背,但在真正的商业领域,极客化的完全去中心化理念并不是一个很好的选择,多中心化技术会是一种很好的平衡。 115 | 116 | 求职者有两种方式录入自己的信息。 117 | 118 | - 一些信息本就被CA机构的数据库所储存(例如,大学会储存学生的GPA,论文发表情况,海外交流经历等信息),但HR需要去非常多的数据库进行调用和查询,因此可以将这些CA机构的信息直接导入。 119 | 120 | - 另一些没有被很好的整理储存的个人数据,而求职者希望展现给HR,将可以通过自己填写表格发起申请,由专门的CA审核后存入区块链。 121 | 122 | ![](media/fbf5c1d8ff912cfa44fabc580f682bcd.png) 123 | 124 | #### 3.1.2 HR流程 125 | 126 | 对于HR来说,使用本系统并不会对他们原来的操作有任何影响。它给予了HR一个信任前提——这些简历的客观信息都是真实可信的,在这个前提下HR进行简历的阅读和筛选,仅此而已。 127 | 128 | 以后的简历将会层次分明的分为两个部分:客观信息+主观阐述。客观信息是求职者从系统中直接调用下载的,有一定格式标准,这一部分在HR的计算机上通过比对哈希进行验证,速度极快。对HR没有技术上的要求。 129 | 130 | ![](media/7303f684c55bb1f3308c3f292de0c953.png) 131 | 132 | ### 3.2 Nebulas 133 | 134 | 本系统使用的区块链技术,来自国内领先的区块链团队Nebulas,利用其提供的RESTful 135 | API,进行智能合约的部署,调用,查询。 136 | 137 | 138 | #### 3.2.1 智能合约 139 | 140 | 本系统目前属于前期测试阶段,因此提供的功能非常简单,就是储存用户的简历,并且可以被查询。 141 | 142 | 合约包括三个函数 143 | * 增加简历 144 | * 查询简历 145 | * 删除简历 146 | 147 | #### 3.2.2 RESTful API 148 | 149 | Nebulas(https://github.com/nebulasio/wiki)提供了比较完整的REST API的接口。 150 | 151 | - 部署节点 152 | 153 | - 部署、实例化智能合约 154 | 155 | - 调用智能合约(查询,插入,更新) 156 | 157 | ### 3.3 隐私保护 158 | 159 | 本系统面临一个很严重的问题,就是用户信息的隐私保护。这也是为什么这么多年来在中心化数据库发展非常成熟的情况下还没有这样一个平台诞生的原因,大量的用户信息被存在一个平台上,如果出现了黑客攻击或者是信息泄露,后果不堪设想。 160 | 161 | 区块链的安全性在目前来说是有保障的。从另一方面来说,并不是所有人都可以在链上查询到简历信息的明文,而是只有用户本人或者是CA才可以看见,其他人访问只能查询得到简历的哈希值用来校验,这些是以硬编码的形式利用智能合约来完成的,这也就满足了用户的隐私保护需求。 162 | 163 | ### 3.4 CA (Certificate Authority) 164 | 165 | 联盟链系统中CA机构可以参考目前https下CA的构成,可以进行分级,由顶级CA进行向下授权,下层CA可以由大学、期刊数据库等组成。这类CA负责信息的迁移,而另一类CA负责用户提交信息的审核,这部分服务可以进行适当的收费。 166 | 167 | ### 3.5 前端 168 | 169 | 现有的前端采用React框架,通过与REST 170 | API交互获取数据并展示在网页上。网页组件采用了react-bootstrap的一些组件实例。 171 | 172 | # 4. 展望 Discussion 173 | 174 | 175 | ### 4.1 HyperLedger Fabric 176 | 177 | 超级账本(hyperledger)是Linux基金会于2015年发起的推进区块链数字技术和交易验证的开源项目,目标是让成员共同合作,共建开放平台,满足来自多个不同行业各种用户案例,并简化业务流程。由于点对点网络的特性,分布式账本技术是完全共享、透明和去中心化的,故非常适合于在金融行业的应用,以及其他的例如制造、银行、保险、物联网等无数个其他行业。通过创建分布式账本的公开标准,实现虚拟和数字形式的价值交换,例如资产合约、能源交易、结婚证书、能够安全和高效低成本的进行追踪和交易。 178 | 179 | HyperLedger Fabric是 180 | HyperLedger多个同时孵化中的子项目之一。2016年6月,Hyperledger Fabric 181 | v0.5开发版公布。截止目前,最新发布版本为2018年3月发布的Fabric-1.1.0。 182 | 183 | Hyperledger 184 | Fabric是一个用来部署和操作拥有准入机制的区块链(联盟链)的模块化可扩展的开源系统。Fabric目前已经被应用在超过400种跨行业的生产系统和分布式账本技术的原型与概念实现中。在不存在万能解决方案的前提下,Fabric是第一个可扩展的运行分布式程序的区块链系统。它支持通过模块化的共识算法定制特定的使用场景和信任模型。Fabric还是第一个对原生加密货币没有系统依赖性,并应用通用编程语言编写分布式程序的区块链。这和一些现在的主流区块链平台形成了鲜明对比,这些平台要么依赖于特定领域语言编写的智能合约,要么依赖于加密货币。此外,它还使用可插拔的成员资格的概念实现了一个准入模型,这将可能会与身份管理系统的行业标准结合。为了支持这种灵活性,Fabric用一种全新的方式实现了私有链的设计,并且改变了区块链处理不确定性、资源枯竭和性能攻击的问题。[6] 185 | 186 | 在简历验证系统的区块链选择上,Fabric由于其社区的活跃性,一直站在区块链最新技术的前沿,不失为一种好的选择。 187 | 188 | ### 4.2 中国个人档案系统 189 | 190 | 国家目前已经建立起一系列有关于个人信息的数据库系统,这些系统可以作为CA,参与将数据上链的过程,完成数据的去中心化储存。 191 | 192 | #### 4.2.1 征信系统 193 | 194 | 2003年,国务院提出5年内建立起社会信用体系;十六届三中全会通过《关于完善社会主义市场经济体制若干问题的决定》提出:要形成以道德为支撑、产业为基础、法律为保障的社会信用体系;“十一五”规划指出要以完善信贷、纳税、合同履约、 195 | 产品质量的信用记录为重点,加快建设社会信用体制;2005年国务院《关于加快电子商务发展的若干意见》中强调:“加快社会信用体系建设”,并要求通过商业运作、专业服务尽快“建立科学、合理、权威、公正的信用服务机构。 196 | 197 | #### 4.2.2 学生档案 198 | 199 | 中国高等教育学生信息网依托中心建立的集高校招生、学籍学历、毕业生就业和国家助学贷款学生个人信息一体化的大型数据仓库,开通了学历查询系统、学籍学历信息管理平台、“阳光高考”信息平台、硕士研究生网上报名和录取检查系统、国家助学贷款学生个人信息系统、学历认证网上办公系统、就业频道等多套电子政务系统和社会信息服务系统。2008年,中心基于新形势下转变政府职能,加强公共服务,全面服务大学生就业的需要,又开通了全国大学生就业公共服务立体化平台。 200 | 201 | #### 4.2.3 中国国家知识产权局专利查询平台 202 | 203 | http://www.sipo.gov.cn/zhfwpt/zljs/ 204 | 205 | 国家知识产权局开通了网上的公开数据库查询系统,整理了所有注册过的专利信息。 206 | 207 | ### 4.3 企业联盟 208 | 209 | 本系统应用的难点不在于技术攻关,而是如何将平台做大,收集到尽可能多的信息,才会有求职者和HR去使用。那么如何推动这样一个平台的诞生呢?企业联盟将是一个很好的选择,一方面企业有自身人力资源的需求,也有资金去推动这个平台发展,另一方面企业联盟与联盟链的架构不谋而合,当更多的企业去作为一个背书节点维持一个账本时,平台的数据的安全性和可靠性就会进一步上升。 210 | 211 | ### 4.4 中国区块链行业发展 212 | 213 | 2017年区块链专利申请中,中国企业申请总数超过其他国家总和,其中阿里巴巴以43件排行第一,其中前20的企业中有13家来自中国。 214 | 215 | 诚然,专利数量并不能代表该领域领先程度,虽然在专利申请上,中国暂时领先,但专利方面的领先能不能说明申请专利只能作为领跑的一个原因,因为创新科技转化为生产力并不是一个简单的专利数量可以支撑起来的,这中间还需要长时间转化,当然还有其他更多的原因,包括产学研一体、应用落地、政策环境等等变量。 216 | 217 | 从产学研角度看,区块链是综合性技术,涵盖了密码学、计算机等学科,目前中国高校并没有开设区块链课程,接下来国内能否提供创新技术的孵化与人才,以及适合创新发展的环境尤为重要。接下来一段时间,同样需要高校在区块链创新中发光发热。 218 | 219 | 从应用落地看,大家公认的区块链应用目前来看只有以比特币为代表的数字货币,区块链技术在金融、征信、溯源、版权等领域的探索仍在试错阶段。从政策环境来看,2018年3月13日,工信部网站发布公告称,将筹建全国区块链和分布式记账技术标准化技术委员会,推进区块链和分布式记账技术的标准化进程。说明我国信息化发展的核心部门已经将区块链标准化纳入工作计划,进一步落实区块链应用进程。 220 | 221 | 参考文献 222 | -------- 223 | 224 | 1. Satoshi Nakamoto.Bitcoin: A Peer-to-Peer Electronic Cash System.2008 225 | 226 | 2. Etherrum.A Next-Generation Smart Contract and Decentralized Application 227 | Platform.2015 228 | 229 | 3. 杜鑫. 高校毕业生简历“注水”愈演愈烈.工人日报. 2012 年/11 月/25 日/第003 版 230 | 231 | 4. 王亚琼. 简历造假影响因素及机制研究.现代商贸工业. 1672-3198(2015)18-0099-04 232 | 233 | 5. 邵彩霞. 浙江省就业市场求职失信行为及其影响因素研究. 时代金融. 2017 年第05 234 | 期中旬刊 235 | 236 | 6. Elli Androulaki, Artem Barger, Vita Bortnikov, Christian Cachin, 237 | Konstantinos Christidis, Angelo De Caro, David Enyeart, Christopher Ferris, 238 | Gennady Laventman, Yacov Manevich, Srinivasan Muralidharan∗, Chet Murthy†, 239 | Binh Nguyen ∗, Manish Sethi, Gari Singh, Keith Smith, Alessandro Sorniotti, 240 | Chrysoula Stathakopoulou, Marko Vukoli´c, Sharon Weed Cocco, Jason 241 | Yellick.IBM.Hyperledger Fabric : A Distributed Operating System for 242 | Permissioned Blockchains.2018 243 | -------------------------------------------------------------------------------- /wwwroot/images/banner1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wwwroot/images/banner3.svg: -------------------------------------------------------------------------------- 1 | banner3b -------------------------------------------------------------------------------- /wwwroot/images/banner4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Components/Veryify.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as Hyperchain from "../Hyperchain" 3 | import { Table, Button } from 'react-bootstrap'; 4 | import * as Utility from '../Utility'; 5 | import * as Nebulas from "../Nebulas" 6 | import * as md5 from 'md5'; 7 | export class Veryify extends React.Component<{}, {resume,resumes,done,resume1hash,resume1Rhash,resume2hash,resume2Rhash,tip1,tip2,verificationResult,name,v1hash,v2hash,tip3}> { 8 | constructor(props) { 9 | super(props); 10 | this.state = { resume: { name: "", age: "", education: "", paper: "", patent: "" },resumes: null, done: false, resume1hash: "", resume1Rhash: "", resume2hash: "", resume2Rhash: "", tip1: "", tip2: "", verificationResult:"",name:"",v1hash:"",v2hash:"",tip3:"" } 11 | } 12 | async getResume() { 13 | const reponse = await fetch("/static/resume.json"); 14 | const data = await reponse.json(); 15 | this.setState({ resumes: data,done:true }); 16 | } 17 | async veryify1() { 18 | const jstr = JSON.stringify(this.state.resumes[0]); 19 | console.log("jstr=" + jstr); 20 | const hash = md5(jstr); 21 | const curUser = localStorage.getItem("HCAccount"); 22 | //const nameHash = md5(this.state.resumes[0].name); 23 | const nameHash = md5(this.state.resumes[0].name); 24 | /*const data = await Hyperchain.InvokeContract([curUser, name], "query"); 25 | const Ret = data.Ret; 26 | var r = Ret.substring(2); 27 | const buf = new Buffer(r, 'hex'); 28 | const s = buf.toString(); 29 | const bchash1 = Utility.trim(s); 30 | */ 31 | const resume = await Nebulas.queryResume(nameHash); 32 | console.log(resume); 33 | let tip = "验证失败!"; 34 | const result = JSON.parse(resume.result.result); 35 | const resumeHash = result.resumeHash; 36 | if (resumeHash == hash) tip = "验证成功!"; 37 | this.setState({ resume1hash: hash, resume1Rhash: resumeHash, tip1: tip }); 38 | } 39 | async veryify2() { 40 | const jstr = JSON.stringify(this.state.resumes[1]); 41 | const hash = md5(jstr); 42 | const curUser = localStorage.getItem("HCAccount"); 43 | const nameHash = md5(this.state.resumes[1].name); 44 | console.log("jstr = " + jstr); 45 | console.log("verify hash= " + hash); 46 | /*const data = await Hyperchain.InvokeContract([curUser, name], "query"); 47 | const Ret = data.Ret; 48 | var r = Ret.substring(2); 49 | const buf = new Buffer(r, 'hex'); 50 | const s = buf.toString(); 51 | const bchash1 = Utility.trim(s); 52 | */ 53 | const resume = await Nebulas.queryResume(nameHash); 54 | let tip = "验证失败!"; 55 | const result = JSON.parse(resume.result.result); 56 | const resumeHash = result.resumeHash; 57 | if (resumeHash == hash) { tip = "验证成功!"; console.log("==");} 58 | this.setState({ resume2hash: hash, resume2Rhash: resumeHash,tip2:tip}); 59 | } 60 | async verify() { 61 | const resume = await Nebulas.queryResume(md5(this.state.name)); 62 | if (resume.result.result == "Error: No resume before.") { 63 | this.setState({ verificationResult: "没有查询到相关简历" }); 64 | } else { 65 | const result = JSON.parse(resume.result.result); 66 | let resumeHash = result.resumeHash; 67 | this.setState({ verificationResult: resumeHash }); 68 | } 69 | } 70 | handleENameChange(e) { 71 | this.setState({ name: e.target.value }); 72 | } 73 | handleNameChange(e) { 74 | var t = this.state.resume; 75 | t.name = e.target.value; 76 | this.setState({ resume: t }); 77 | } 78 | handleAgeChange(e) { 79 | var t = this.state.resume; 80 | t.age = e.target.value; 81 | this.setState({ resume: t }); 82 | } 83 | handleEducationChange(e) { 84 | var t = this.state.resume; 85 | t.education = e.target.value; 86 | this.setState({ resume: t }); 87 | } 88 | handlePaperChange(e) { 89 | var t = this.state.resume; 90 | t.paper = e.target.value; 91 | this.setState({ resume: t }); 92 | } 93 | handlePatentChange(e) { 94 | var t = this.state.resume; 95 | t.patent = e.target.value; 96 | this.setState({ resume: t }); 97 | } 98 | async verifyResume() { 99 | const jstr = JSON.stringify(this.state.resume); 100 | const hash = md5(jstr); 101 | const curUser = localStorage.getItem("HCAccount"); 102 | const nameHash = md5(this.state.resume.name); 103 | const resume = await Nebulas.queryResume(nameHash); 104 | let tip = "验证失败!"; 105 | const result = JSON.parse(resume.result.result); 106 | const resumeHash = result.resumeHash; 107 | if (resumeHash == hash) { tip = "验证成功!"; console.log("=="); } 108 | this.setState({ v1hash: hash, v2hash: resumeHash, tip3: tip }); 109 | } 110 | render() { 111 | let res = null; 112 | if (this.state.done) { 113 | res =
    114 |
    115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 |
    条目内容
    姓名{this.state.resumes[0].name}
    年龄{this.state.resumes[0].age}
    学历{this.state.resumes[0].education}
    论文数量{this.state.resumes[0].paper}
    专利数量{this.state.resumes[0].patent}
    提交hash{this.state.resume1hash}
    区块链hash{this.state.resume1Rhash}
    153 | 154 |

    {this.state.tip1}

    155 |
    156 | 157 |
    158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 |
    条目内容
    姓名{this.state.resumes[1].name}
    年龄{this.state.resumes[1].age}
    学历{this.state.resumes[1].education}
    论文数量{this.state.resumes[1].paper}
    专利数量{this.state.resumes[1].patent}
    提交hash{this.state.resume2hash}
    区块链hash{this.state.resume2Rhash}
    196 | 197 |

    {this.state.tip2}

    198 |
    199 |
    ; 200 | } 201 | return
    204 |

    Step 1 (根据姓名查询简历哈希)

    205 |
    206 |
    207 |

    姓名

    208 |
    209 |
    查询哈希:{this.state.verificationResult}
    210 |
    211 |

    Step 2 (验证简历内容是否为真)

    212 |
    213 |
    214 | 215 | 216 |
    217 |
    218 | 219 | 220 |
    221 |
    222 | 223 | 224 |
    225 |
    226 | 227 | 228 |
    229 |
    230 | 231 | 232 |
    233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 |
    提交hash{this.state.v1hash}
    区块链hash{this.state.v2hash}
    247 |

    {this.state.tip3}

    248 |
    249 | 250 | 251 |

    Step 3 (模拟真实HR业务工作场景批量导入简历JSON)

    252 | 253 | {res} 254 |
    ; 255 | } 256 | } -------------------------------------------------------------------------------- /wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js: -------------------------------------------------------------------------------- 1 | /*! 2 | ** Unobtrusive validation support library for jQuery and jQuery Validate 3 | ** Copyright (C) Microsoft Corporation. All rights reserved. 4 | */ 5 | 6 | /*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */ 7 | /*global document: false, jQuery: false */ 8 | 9 | (function ($) { 10 | var $jQval = $.validator, 11 | adapters, 12 | data_validation = "unobtrusiveValidation"; 13 | 14 | function setValidationValues(options, ruleName, value) { 15 | options.rules[ruleName] = value; 16 | if (options.message) { 17 | options.messages[ruleName] = options.message; 18 | } 19 | } 20 | 21 | function splitAndTrim(value) { 22 | return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g); 23 | } 24 | 25 | function escapeAttributeValue(value) { 26 | // As mentioned on http://api.jquery.com/category/selectors/ 27 | return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1"); 28 | } 29 | 30 | function getModelPrefix(fieldName) { 31 | return fieldName.substr(0, fieldName.lastIndexOf(".") + 1); 32 | } 33 | 34 | function appendModelPrefix(value, prefix) { 35 | if (value.indexOf("*.") === 0) { 36 | value = value.replace("*.", prefix); 37 | } 38 | return value; 39 | } 40 | 41 | function onError(error, inputElement) { // 'this' is the form element 42 | var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"), 43 | replaceAttrValue = container.attr("data-valmsg-replace"), 44 | replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null; 45 | 46 | container.removeClass("field-validation-valid").addClass("field-validation-error"); 47 | error.data("unobtrusiveContainer", container); 48 | 49 | if (replace) { 50 | container.empty(); 51 | error.removeClass("input-validation-error").appendTo(container); 52 | } 53 | else { 54 | error.hide(); 55 | } 56 | } 57 | 58 | function onErrors(event, validator) { // 'this' is the form element 59 | var container = $(this).find("[data-valmsg-summary=true]"), 60 | list = container.find("ul"); 61 | 62 | if (list && list.length && validator.errorList.length) { 63 | list.empty(); 64 | container.addClass("validation-summary-errors").removeClass("validation-summary-valid"); 65 | 66 | $.each(validator.errorList, function () { 67 | $("
  • ").html(this.message).appendTo(list); 68 | }); 69 | } 70 | } 71 | 72 | function onSuccess(error) { // 'this' is the form element 73 | var container = error.data("unobtrusiveContainer"); 74 | 75 | if (container) { 76 | var replaceAttrValue = container.attr("data-valmsg-replace"), 77 | replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) : null; 78 | 79 | container.addClass("field-validation-valid").removeClass("field-validation-error"); 80 | error.removeData("unobtrusiveContainer"); 81 | 82 | if (replace) { 83 | container.empty(); 84 | } 85 | } 86 | } 87 | 88 | function onReset(event) { // 'this' is the form element 89 | var $form = $(this), 90 | key = '__jquery_unobtrusive_validation_form_reset'; 91 | if ($form.data(key)) { 92 | return; 93 | } 94 | // Set a flag that indicates we're currently resetting the form. 95 | $form.data(key, true); 96 | try { 97 | $form.data("validator").resetForm(); 98 | } finally { 99 | $form.removeData(key); 100 | } 101 | 102 | $form.find(".validation-summary-errors") 103 | .addClass("validation-summary-valid") 104 | .removeClass("validation-summary-errors"); 105 | $form.find(".field-validation-error") 106 | .addClass("field-validation-valid") 107 | .removeClass("field-validation-error") 108 | .removeData("unobtrusiveContainer") 109 | .find(">*") // If we were using valmsg-replace, get the underlying error 110 | .removeData("unobtrusiveContainer"); 111 | } 112 | 113 | function validationInfo(form) { 114 | var $form = $(form), 115 | result = $form.data(data_validation), 116 | onResetProxy = $.proxy(onReset, form), 117 | defaultOptions = $jQval.unobtrusive.options || {}, 118 | execInContext = function (name, args) { 119 | var func = defaultOptions[name]; 120 | func && $.isFunction(func) && func.apply(form, args); 121 | } 122 | 123 | if (!result) { 124 | result = { 125 | options: { // options structure passed to jQuery Validate's validate() method 126 | errorClass: defaultOptions.errorClass || "input-validation-error", 127 | errorElement: defaultOptions.errorElement || "span", 128 | errorPlacement: function () { 129 | onError.apply(form, arguments); 130 | execInContext("errorPlacement", arguments); 131 | }, 132 | invalidHandler: function () { 133 | onErrors.apply(form, arguments); 134 | execInContext("invalidHandler", arguments); 135 | }, 136 | messages: {}, 137 | rules: {}, 138 | success: function () { 139 | onSuccess.apply(form, arguments); 140 | execInContext("success", arguments); 141 | } 142 | }, 143 | attachValidation: function () { 144 | $form 145 | .off("reset." + data_validation, onResetProxy) 146 | .on("reset." + data_validation, onResetProxy) 147 | .validate(this.options); 148 | }, 149 | validate: function () { // a validation function that is called by unobtrusive Ajax 150 | $form.validate(); 151 | return $form.valid(); 152 | } 153 | }; 154 | $form.data(data_validation, result); 155 | } 156 | 157 | return result; 158 | } 159 | 160 | $jQval.unobtrusive = { 161 | adapters: [], 162 | 163 | parseElement: function (element, skipAttach) { 164 | /// 165 | /// Parses a single HTML element for unobtrusive validation attributes. 166 | /// 167 | /// The HTML element to be parsed. 168 | /// [Optional] true to skip attaching the 169 | /// validation to the form. If parsing just this single element, you should specify true. 170 | /// If parsing several elements, you should specify false, and manually attach the validation 171 | /// to the form when you are finished. The default is false. 172 | var $element = $(element), 173 | form = $element.parents("form")[0], 174 | valInfo, rules, messages; 175 | 176 | if (!form) { // Cannot do client-side validation without a form 177 | return; 178 | } 179 | 180 | valInfo = validationInfo(form); 181 | valInfo.options.rules[element.name] = rules = {}; 182 | valInfo.options.messages[element.name] = messages = {}; 183 | 184 | $.each(this.adapters, function () { 185 | var prefix = "data-val-" + this.name, 186 | message = $element.attr(prefix), 187 | paramValues = {}; 188 | 189 | if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy) 190 | prefix += "-"; 191 | 192 | $.each(this.params, function () { 193 | paramValues[this] = $element.attr(prefix + this); 194 | }); 195 | 196 | this.adapt({ 197 | element: element, 198 | form: form, 199 | message: message, 200 | params: paramValues, 201 | rules: rules, 202 | messages: messages 203 | }); 204 | } 205 | }); 206 | 207 | $.extend(rules, { "__dummy__": true }); 208 | 209 | if (!skipAttach) { 210 | valInfo.attachValidation(); 211 | } 212 | }, 213 | 214 | parse: function (selector) { 215 | /// 216 | /// Parses all the HTML elements in the specified selector. It looks for input elements decorated 217 | /// with the [data-val=true] attribute value and enables validation according to the data-val-* 218 | /// attribute values. 219 | /// 220 | /// Any valid jQuery selector. 221 | 222 | // $forms includes all forms in selector's DOM hierarchy (parent, children and self) that have at least one 223 | // element with data-val=true 224 | var $selector = $(selector), 225 | $forms = $selector.parents() 226 | .addBack() 227 | .filter("form") 228 | .add($selector.find("form")) 229 | .has("[data-val=true]"); 230 | 231 | $selector.find("[data-val=true]").each(function () { 232 | $jQval.unobtrusive.parseElement(this, true); 233 | }); 234 | 235 | $forms.each(function () { 236 | var info = validationInfo(this); 237 | if (info) { 238 | info.attachValidation(); 239 | } 240 | }); 241 | } 242 | }; 243 | 244 | adapters = $jQval.unobtrusive.adapters; 245 | 246 | adapters.add = function (adapterName, params, fn) { 247 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation. 248 | /// The name of the adapter to be added. This matches the name used 249 | /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). 250 | /// [Optional] An array of parameter names (strings) that will 251 | /// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and 252 | /// mmmm is the parameter name). 253 | /// The function to call, which adapts the values from the HTML 254 | /// attributes into jQuery Validate rules and/or messages. 255 | /// 256 | if (!fn) { // Called with no params, just a function 257 | fn = params; 258 | params = []; 259 | } 260 | this.push({ name: adapterName, params: params, adapt: fn }); 261 | return this; 262 | }; 263 | 264 | adapters.addBool = function (adapterName, ruleName) { 265 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where 266 | /// the jQuery Validate validation rule has no parameter values. 267 | /// The name of the adapter to be added. This matches the name used 268 | /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). 269 | /// [Optional] The name of the jQuery Validate rule. If not provided, the value 270 | /// of adapterName will be used instead. 271 | /// 272 | return this.add(adapterName, function (options) { 273 | setValidationValues(options, ruleName || adapterName, true); 274 | }); 275 | }; 276 | 277 | adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) { 278 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where 279 | /// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and 280 | /// one for min-and-max). The HTML parameters are expected to be named -min and -max. 281 | /// The name of the adapter to be added. This matches the name used 282 | /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). 283 | /// The name of the jQuery Validate rule to be used when you only 284 | /// have a minimum value. 285 | /// The name of the jQuery Validate rule to be used when you only 286 | /// have a maximum value. 287 | /// The name of the jQuery Validate rule to be used when you 288 | /// have both a minimum and maximum value. 289 | /// [Optional] The name of the HTML attribute that 290 | /// contains the minimum value. The default is "min". 291 | /// [Optional] The name of the HTML attribute that 292 | /// contains the maximum value. The default is "max". 293 | /// 294 | return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) { 295 | var min = options.params.min, 296 | max = options.params.max; 297 | 298 | if (min && max) { 299 | setValidationValues(options, minMaxRuleName, [min, max]); 300 | } 301 | else if (min) { 302 | setValidationValues(options, minRuleName, min); 303 | } 304 | else if (max) { 305 | setValidationValues(options, maxRuleName, max); 306 | } 307 | }); 308 | }; 309 | 310 | adapters.addSingleVal = function (adapterName, attribute, ruleName) { 311 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where 312 | /// the jQuery Validate validation rule has a single value. 313 | /// The name of the adapter to be added. This matches the name used 314 | /// in the data-val-nnnn HTML attribute(where nnnn is the adapter name). 315 | /// [Optional] The name of the HTML attribute that contains the value. 316 | /// The default is "val". 317 | /// [Optional] The name of the jQuery Validate rule. If not provided, the value 318 | /// of adapterName will be used instead. 319 | /// 320 | return this.add(adapterName, [attribute || "val"], function (options) { 321 | setValidationValues(options, ruleName || adapterName, options.params[attribute]); 322 | }); 323 | }; 324 | 325 | $jQval.addMethod("__dummy__", function (value, element, params) { 326 | return true; 327 | }); 328 | 329 | $jQval.addMethod("regex", function (value, element, params) { 330 | var match; 331 | if (this.optional(element)) { 332 | return true; 333 | } 334 | 335 | match = new RegExp(params).exec(value); 336 | return (match && (match.index === 0) && (match[0].length === value.length)); 337 | }); 338 | 339 | $jQval.addMethod("nonalphamin", function (value, element, nonalphamin) { 340 | var match; 341 | if (nonalphamin) { 342 | match = value.match(/\W/g); 343 | match = match && match.length >= nonalphamin; 344 | } 345 | return match; 346 | }); 347 | 348 | if ($jQval.methods.extension) { 349 | adapters.addSingleVal("accept", "mimtype"); 350 | adapters.addSingleVal("extension", "extension"); 351 | } else { 352 | // for backward compatibility, when the 'extension' validation method does not exist, such as with versions 353 | // of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for 354 | // validating the extension, and ignore mime-type validations as they are not supported. 355 | adapters.addSingleVal("extension", "extension", "accept"); 356 | } 357 | 358 | adapters.addSingleVal("regex", "pattern"); 359 | adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"); 360 | adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range"); 361 | adapters.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength"); 362 | adapters.add("equalto", ["other"], function (options) { 363 | var prefix = getModelPrefix(options.element.name), 364 | other = options.params.other, 365 | fullOtherName = appendModelPrefix(other, prefix), 366 | element = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(fullOtherName) + "']")[0]; 367 | 368 | setValidationValues(options, "equalTo", element); 369 | }); 370 | adapters.add("required", function (options) { 371 | // jQuery Validate equates "required" with "mandatory" for checkbox elements 372 | if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") { 373 | setValidationValues(options, "required", true); 374 | } 375 | }); 376 | adapters.add("remote", ["url", "type", "additionalfields"], function (options) { 377 | var value = { 378 | url: options.params.url, 379 | type: options.params.type || "GET", 380 | data: {} 381 | }, 382 | prefix = getModelPrefix(options.element.name); 383 | 384 | $.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) { 385 | var paramName = appendModelPrefix(fieldName, prefix); 386 | value.data[paramName] = function () { 387 | var field = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']"); 388 | // For checkboxes and radio buttons, only pick up values from checked fields. 389 | if (field.is(":checkbox")) { 390 | return field.filter(":checked").val() || field.filter(":hidden").val() || ''; 391 | } 392 | else if (field.is(":radio")) { 393 | return field.filter(":checked").val() || ''; 394 | } 395 | return field.val(); 396 | }; 397 | }); 398 | 399 | setValidationValues(options, "remote", value); 400 | }); 401 | adapters.add("password", ["min", "nonalphamin", "regex"], function (options) { 402 | if (options.params.min) { 403 | setValidationValues(options, "minlength", options.params.min); 404 | } 405 | if (options.params.nonalphamin) { 406 | setValidationValues(options, "nonalphamin", options.params.nonalphamin); 407 | } 408 | if (options.params.regex) { 409 | setValidationValues(options, "regex", options.params.regex); 410 | } 411 | }); 412 | 413 | $(function () { 414 | $jQval.unobtrusive.parse(document); 415 | }); 416 | }(jQuery)); -------------------------------------------------------------------------------- /wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA","sourcesContent":["/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n .box-shadow(none);\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n border-radius: @navbar-border-radius;\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} --------------------------------------------------------------------------------