"
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
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
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
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 |
27 |
28 | 我的简历
29 |
30 |
31 | 验证简历
32 |
33 |
34 |
35 |
36 |
37 | {userCenter}
38 |
39 | {isLogin?name+"/注销":"登录"}
40 |
41 |
42 | 注册
43 |
44 |
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 |
39 | login
40 |
41 |
42 | createChannel
43 |
44 |
45 | joinChannel
46 |
47 |
48 | installChaincode
49 |
50 |
51 | instantiateChaincode
52 |
53 |
54 | invoke
55 |
56 |
57 | query
58 |
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 | manager
60 | 123456
61 |
62 |
63 |
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 |
21 |
39 |
40 |
41 | @RenderBody()
42 |
43 |
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 | {resume.name}
57 |
58 |
59 | 年龄
60 | {resume.age}
61 |
62 |
63 | 学历
64 | {resume.education}
65 |
66 |
67 | 论文数量
68 | {resume.paper}
69 |
70 |
71 | 专利数量
72 | {resume.patent}
73 |
74 |
75 |
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 |
71 | 登陆
72 |
73 |
74 | 编译合约
75 |
76 |
77 | 部署合约
78 |
79 |
80 | 调用合约
81 |
82 |
83 | 查询合约
84 |
85 |
86 | 查询自己
87 |
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 | Token
114 | {this.state.token}
115 |
116 |
117 | Result
118 | {this.state.result}
119 |
120 |
121 | State
122 | {this.state.tip}
123 |
124 |
125 |
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 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Learn how to build ASP.NET apps that can run anywhere.
20 |
21 | Learn More
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | There are powerful new features in Visual Studio for building modern web apps.
31 |
32 | Learn More
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Bring in libraries from NuGet and npm, and automate tasks using Grunt or Gulp.
42 |
43 | Learn More
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | Learn how Microsoft's Azure cloud platform allows you to build, deploy, and scale web apps.
53 |
54 | Learn More
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Previous
63 |
64 |
65 |
66 | Next
67 |
68 |
69 |
70 |
71 |
72 |
Application uses
73 |
74 | Sample pages using ASP.NET Core Razor Pages
75 | Theming using Bootstrap
76 |
77 |
78 |
88 |
100 |
101 |
Run & Deploy
102 |
107 |
108 |
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 |
41 |
42 |
43 |
48 |
49 |
50 |
51 |
52 | 条目
53 | 内容
54 |
55 |
56 |
57 |
58 | 姓名
59 | 比尔·盖茨
60 |
61 |
62 | 年龄
63 | 63
64 |
65 |
66 | 学历
67 | 哈佛大学肄业
68 |
69 |
70 | 论文数量
71 | 1
72 |
73 |
74 | 专利数量
75 | 3000/年
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | 条目
85 | 内容
86 |
87 |
88 |
89 |
90 | 姓名
91 | 御坂美琴
92 |
93 |
94 | 年龄
95 | 16
96 |
97 |
98 | 学历
99 | 常盘台中学
100 |
101 |
102 | 论文数量
103 | 0
104 |
105 |
106 | 专利数量
107 | 1(超电磁炮)
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | 条目
117 | 内容
118 |
119 |
120 |
121 |
122 | 姓名
123 | 吴朝晖
124 |
125 |
126 | 年龄
127 | 52
128 |
129 |
130 | 学历
131 | 浙江大学博士
132 |
133 |
134 | 论文数量
135 | 180
136 |
137 |
138 | 专利数量
139 | 120
140 |
141 |
142 |
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 |
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 | {data.name}
137 |
138 |
139 | 姓名哈希
140 | {nameHash}
141 |
142 |
143 | 地址
144 | qdw
145 |
146 |
147 | 年龄
148 | {data.age}
149 |
150 |
151 | 学历
152 | {data.education}
153 |
154 |
155 | 论文数量
156 | {data.paper}
157 |
158 |
159 | 专利数量
160 | {data.patent}
161 |
162 |
163 |
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 | 
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 | 
123 |
124 | #### 3.1.2 HR流程
125 |
126 | 对于HR来说,使用本系统并不会对他们原来的操作有任何影响。它给予了HR一个信任前提——这些简历的客观信息都是真实可信的,在这个前提下HR进行简历的阅读和筛选,仅此而已。
127 |
128 | 以后的简历将会层次分明的分为两个部分:客观信息+主观阐述。客观信息是求职者从系统中直接调用下载的,有一定格式标准,这一部分在HR的计算机上通过比对哈希进行验证,速度极快。对HR没有技术上的要求。
129 |
130 | 
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 | {this.state.resumes[0].name}
126 |
127 |
128 | 年龄
129 | {this.state.resumes[0].age}
130 |
131 |
132 | 学历
133 | {this.state.resumes[0].education}
134 |
135 |
136 | 论文数量
137 | {this.state.resumes[0].paper}
138 |
139 |
140 | 专利数量
141 | {this.state.resumes[0].patent}
142 |
143 |
144 | 提交hash
145 | {this.state.resume1hash}
146 |
147 |
148 | 区块链hash
149 | {this.state.resume1Rhash}
150 |
151 |
152 |
153 |
验证
154 |
{this.state.tip1}
155 |
156 |
157 |
158 |
159 |
160 |
161 | 条目
162 | 内容
163 |
164 |
165 |
166 |
167 | 姓名
168 | {this.state.resumes[1].name}
169 |
170 |
171 | 年龄
172 | {this.state.resumes[1].age}
173 |
174 |
175 | 学历
176 | {this.state.resumes[1].education}
177 |
178 |
179 | 论文数量
180 | {this.state.resumes[1].paper}
181 |
182 |
183 | 专利数量
184 | {this.state.resumes[1].patent}
185 |
186 |
187 | 提交hash
188 | {this.state.resume2hash}
189 |
190 |
191 | 区块链hash
192 | {this.state.resume2Rhash}
193 |
194 |
195 |
196 |
验证
197 |
{this.state.tip2}
198 |
199 |
;
200 | }
201 | return
204 |
Step 1 (根据姓名查询简历哈希)
205 |
206 |
209 |
查询哈希:{this.state.verificationResult}
210 |
211 |
Step 2 (验证简历内容是否为真)
212 |
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"]}
--------------------------------------------------------------------------------