Repository: takashiki/Ourls
Branch: master
Commit: e7789805f044
Files: 15
Total size: 15.7 KB
Directory structure:
gitextract_il3ij_3v/
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── app/
│ ├── components/
│ │ └── Hash.php
│ ├── config.sample.php
│ ├── helpers.php
│ ├── routes.php
│ └── views/
│ └── index.php
├── composer.json
├── public/
│ ├── .htaccess
│ ├── css/
│ │ └── app.css
│ ├── index.php
│ └── js/
│ └── index.js
└── urls.sql
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.idea
/app/config.php
vendor
/config.yaml
================================================
FILE: .travis.yml
================================================
language: php
php:
- '5.6'
- '7.0'
- '7.1'
before_install:
- composer install
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2011 Mike Cao <mike@mikecao.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
# Ourls
[](https://packagist.org/packages/takashiki/ourls)
[](https://packagist.org/packages/takashiki/ourls)
[](https://packagist.org/packages/takashiki/ourls)
[](https://packagist.org/packages/takashiki/ourls)
Ourls是一个基于发号和hashid的短网址服务,灵感来源于知乎上关于短址算法的一个讨论——
[http://www.zhihu.com/question/29270034](http://www.zhihu.com/question/29270034)。
## 特征/Feature
Ourls会根据sha1值来判断原url在数据库中是否已存在,若不存在则新增记录后对记录id进行hash,产生短网址。
Ourls会对输入的url进行标准化处理,若为缺少scheme的url,会默认自动加上`http://`,
并且会对url的query参数进行排序和urlencode等。
## 演示/Demo
[在线演示/Online Demo](http://skyx.in)
## 安装/Install
下载源码后运行`composer install`安装依赖包,或者运行`composer create-project takashiki/ourls`。
然后将urls.sql导入数据库中,将app目录下config.sample.php重命名为config.php并按自己实际情况修改相关配置项。
> git clone and composer install or composer create-project takashiki/ourls
> import urls.sql to your database
> rename app/config.sample.php to app/config.php
> modify the config file according to your situation
### License
Ourls is open-sourced software licensed under the
[MIT license](http://opensource.org/licenses/MIT)
================================================
FILE: app/components/Hash.php
================================================
<?php
namespace app\components;
use Hashids\Hashids;
class Hash
{
public $hashids;
public function __construct(array $params)
{
$this->hashids = new Hashids(
$params['salt'],
$params['length'],
$params['alphabet']
);
}
public function encode($id)
{
return $this->hashids->encode($id);
}
public function decode($hash)
{
$id = $this->hashids->decode($hash);
return $id ? $id[0] : false;
}
}
================================================
FILE: app/config.sample.php
================================================
<?php
return [
'debug' => true,
'base_url' => 'YourSiteUrl',
'hash' => [
'salt' => 'SomeRandomKey',
'length' => 5,
'alphabet' => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890',
],
'db' => [
'database_type' => 'mysql',
'database_name' => 'name',
'server' => 'localhost',
'username' => 'your_username',
'password' => 'your_password',
'charset' => 'utf8',
'port' => 3306,
'option' => [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
],
],
'db_read' => [
'database_type' => 'mysql',
'database_name' => 'name',
'server' => 'localhost',
'username' => 'your_username',
'password' => 'your_password',
'charset' => 'utf8',
'port' => 3306,
'option' => [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
],
],
'settings' => [
'external_js' => null,
],
'proxies' => [
'127.0.0.0/8',
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'fd00::/8',
],
];
================================================
FILE: app/helpers.php
================================================
<?php
use Etechnika\IdnaConvert\IdnaConvert;
if (!function_exists('avg')) {
function avg(array $data)
{
return array_sum($data) / count($data);
}
}
if (!function_exists('url_modify')) {
function url_modify($url, $defaultScheme = 'http')
{
if (parse_url($url, PHP_URL_SCHEME) == null) {
$url = $defaultScheme.'://'.trim($url, '/');
}
$url = (new URL\Normalizer($url, true, true))->normalize();
if (filter_var(IdnaConvert::encodeString($url), FILTER_VALIDATE_URL) === false) {
return false;
} else {
$fragment = parse_url($url, PHP_URL_FRAGMENT);
return str_replace('#'.$fragment, '#'.urldecode($fragment), $url);
}
}
}
if (!function_exists('real_remote_addr')) {
function real_remote_addr()
{
$ip = Flight::request()->ip;
$proxy = Flight::request()->proxy_ip;
if ('' != $proxy && Flight::get('proxies')->match($ip)) {
return $proxy;
} else {
return $ip;
}
}
}
/*
* Registers a class and set a variable to framework method.
*
* @param string $name Method name
* @param string $class Class name
* @param array $params Class initialization parameters
* @param callback $callback Function to call after object instantiation
* @throws \Exception If trying to map over a framework method
*/
Flight::map('instance', function ($name, $class, array $params = [], $callback = null) {
Flight::register($name, $class, $params, $callback);
Flight::set($name, Flight::{$name}());
});
================================================
FILE: app/routes.php
================================================
<?php
Flight::route('/', function () {
Flight::render('index.php');
});
Flight::route('/shorten', function () {
$url = url_modify(Flight::request()->query['url']);
if ($url) {
if (strpos($url, Flight::get('flight.base_url')) !== false) {
Flight::json(['status' => 0, 'msg' => '该地址无法被缩短']);
} else {
$sha1 = sha1($url);
$store = Flight::get('db_read')->select('urls', ['id'], [
'sha1' => $sha1,
]);
if (!$store) {
$id = Flight::get('db')->insert('urls', [
'sha1' => $sha1,
'url' => $url,
'create_at' => time(),
'creator' => ip2long(real_remote_addr()),
]);
} else {
$id = $store[0]['id'];
}
$s_url = Flight::get('flight.base_url').Flight::get('hash')->encode($id);
Flight::json(['status' => 1, 's_url' => $s_url]);
}
} else {
Flight::json(['status' => 0, 'msg' => '请传入正确的url']);
}
});
Flight::route('/expand', function () {
$s_url = Flight::request()->query['s_url'];
if ($s_url) {
$hash = str_replace(Flight::get('flight.base_url'), '', $s_url);
if (!preg_match('/^['.Flight::get('alphabet').']+$/', $hash)) {
Flight::json(['status' => 0, 'msg' => '短址不正确']);
} else {
$id = Flight::get('hash')->decode($hash);
if (!$id) {
Flight::json(['status' => 0, 'msg' => '短址无法解析']);
} else {
$store = Flight::get('db_read')->select('urls', ['url'], [
'id' => $id,
]);
if (!$store) {
Flight::json(['status' => 0, 'msg' => '地址不存在']);
} else {
Flight::json(['status' => 1, 'url' => $store[0]['url']]);
}
}
}
}
});
Flight::route('/@hash', function ($hash) {
$id = Flight::get('hash')->decode($hash);
if (!$id) {
Flight::notFound('短址无法解析');
} else {
$store = Flight::get('db_read')->select('urls', ['url'], [
'id' => $id,
]);
if (!$store) {
Flight::notFound('地址不存在');
} else {
Flight::get('db')->update('urls', ['count[+]' => 1], [
'id' => $id,
]);
Flight::redirect($store[0]['url'], 302);
}
}
});
Flight::map('notFound', function ($message) {
Flight::response()->status(404)
->header('content-type', 'text/html; charset=utf-8')
->write(
'<h1>404 页面未找到</h1>'.
"<h3>{$message}</h3>".
'<p><a href="'.Flight::get('flight.base_url').'">回到首页</a></p>'.
str_repeat(' ', 512)
)
->send();
});
Flight::map('error', function (Exception $ex) {
$message = Flight::get('flight.log_errors') ? $ex->getTraceAsString() : '出错了';
Flight::response()->status(500)
->header('content-type', 'text/html; charset=utf-8')
->write(
'<h1>500 服务器内部错误</h1>'.
"<h3>{$message}</h3>".
'<p><a href="'.Flight::get('flight.base_url').'">回到首页</a></p>'.
str_repeat(' ', 512)
)
->send();
});
================================================
FILE: app/views/index.php
================================================
<!doctype html>
<html class="no-js">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="">
<meta name="keywords" content="">
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Ourls</title>
<!-- Set render engine for 360 browser -->
<meta name="renderer" content="webkit">
<!-- No Baidu Siteapp-->
<meta http-equiv="Cache-Control" content="no-siteapp"/>
<!-- Add to homescreen for Chrome on Android -->
<meta name="mobile-web-app-capable" content="yes">
<!-- Add to homescreen for Safari on iOS -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Amaze UI"/>
<meta name="msapplication-TileColor" content="#0e90d2">
<link href="//cdn.bootcss.com/amazeui/2.5.2/css/amazeui.min.css" rel="stylesheet">
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<a href="https://github.com/takashiki/ourls">
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/38ef81f8aca64bb9a64448d0d70f1308ef5341ab/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6461726b626c75655f3132313632312e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png">
</a>
<div class="header">
<div class="am-g">
<h1>Ourls</h1>
<p>Url Shorten Service<br>基于发号加hash id的短网址服务</p>
</div>
<hr>
</div>
<div class="am-g">
<div id="content" class="am-u-lg-6 am-u-md-8 am-u-sm-centered">
<form class="am-form">
<input type="url" name="" id="url" value="" placeholder="请在此填写你要转换的长网址或短址">
<br>
<div class="am-cf">
<input type="button" id="shorten" value="转换短址" class="am-btn am-btn-primary am-btn-sm am-fl">
<input type="button" id="expand" value="还原短址" class="am-btn am-btn-default am-btn-sm am-fr">
</div>
</form>
<div id="qrcode" class="am-hide am-center am-img-thumbnail am-img-responsive" style="width: 206px;height: 206px"></div>
<hr>
<p>© <?= date('Y') ?> <a href="https://github.com/takashiki/ourls" target="_blank">Ourls</a> . Licensed under MIT license.</p>
</div>
</div>
<!--[if (gte IE 9)|!(IE)]><!-->
<script src="//cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>
<!--<![endif]-->
<!--[if lte IE 8 ]>
<script src="http://libs.baidu.com/jquery/1.11.3/jquery.min.js"></script>
<script src="http://cdn.staticfile.org/modernizr/2.8.3/modernizr.js"></script>
<script src="http://cdn.amazeui.org/amazeui/2.4.2/js/amazeui.ie8polyfill.min.js"></script>
<![endif]-->
<script src="//cdn.bootcss.com/amazeui/2.5.2/js/amazeui.min.js"></script>
<script src="//cdn.bootcss.com/validator/4.0.5/validator.min.js"></script>
<script src="//cdn.bootcss.com/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>
<script src="js/index.js"></script>
<?php if (!empty(Flight::get('flight.settings')['external_js'])): ?>
<script src="<?= Flight::get('flight.settings')['external_js'] ?>"></script>
<?php endif ?>
</body>
</html>
================================================
FILE: composer.json
================================================
{
"name": "takashiki/ourls",
"description": "A url shorten service system base on hash id",
"keywords": ["url shorten", "hashids"],
"homepage": "https://github.com/takashiki/ourls",
"license": "MIT",
"authors": [
{
"name": "takashiki",
"email": "857995137@qq.com",
"homepage": "http://blog.skyx.in/"
}
],
"require": {
"php": ">=5.6.4",
"catfan/medoo": "^0.9.8",
"etechnika/idna-convert": "^1.1",
"glenscott/url-normalizer": "*",
"hashids/hashids": "^2.0",
"mikecao/flight": "^1.2",
"wikimedia/ip-set": "1.1.0"
},
"autoload": {
"psr-4": {
"app\\": "app/"
}
},
"config": {
"preferred-install": "dist",
"sort-packages": true,
"optimize-autoloader": true
}
}
================================================
FILE: public/.htaccess
================================================
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
================================================
FILE: public/css/app.css
================================================
.header {
text-align: center;
}
.header h1 {
font-size: 200%;
color: #333;
margin-top: 30px;
}
================================================
FILE: public/index.php
================================================
<?php
require __DIR__.'/../vendor/autoload.php';
require __DIR__.'/../app/helpers.php';
require __DIR__.'/../app/routes.php';
$config = require __DIR__.'/../app/config.php';
Flight::set('flight.log_errors', $config['debug']);
Flight::set('flight.base_url', $config['base_url']);
Flight::set('flight.settings', $config['settings']);
Flight::set('flight.views.path', __DIR__.'/../app/views');
Flight::set('alphabet', $config['hash']['alphabet']);
Flight::instance('hash', '\app\components\Hash', [$config['hash']]);
Flight::instance('db', 'medoo', [$config['db']]);
Flight::instance('db_read', 'medoo', [$config['db_read']]);
Flight::instance('proxies', '\IPSet\IPSet', [$config['proxies']]);
Flight::start();
================================================
FILE: public/js/index.js
================================================
$('#shorten').click(function() {
var raw_url = $('#url').val();
if (validator.isURL(raw_url)) {
var url = encodeURIComponent(raw_url);
$.getJSON(
'shorten?url=' + url,
function (data) {
if (data.status == 1) {
$('#url').val(data.s_url);
var qrcode = $('#qrcode');
qrcode.qrcode({
width: 200,
height: 200,
text: data.s_url
});
qrcode.removeClass('am-hide');
} else {
alert(data.msg);
}
}
)
} else {
alert('请输入正确的url');
}
});
$('#expand').click(function() {
var s_url = $('#url').val();
$.getJSON(
'expand?s_url=' + s_url,
function(data) {
if (data.status == 1) {
$('#url').val(data.url);
var qrcode = $('#qrcode');
qrcode.addClass('am-hide');
qrcode.html('');
} else {
alert(data.msg);
}
}
)
});
================================================
FILE: urls.sql
================================================
CREATE TABLE `urls` (
`id` INT (11) NOT NULL AUTO_INCREMENT,
`sha1` CHAR (40) NOT NULL,
`url` VARCHAR (255) NOT NULL,
`create_at` INT (11) NOT NULL,
`creator` INT (11) unsigned NOT NULL DEFAULT '0',
`count` INT (11) NOT NULL DEFAULT '0',
`status` TINYINT(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
INDEX (`sha1`),
INDEX (`create_at`),
INDEX (`creator`),
INDEX (`count`),
INDEX (`status`)
) ENGINE = INNODB CHARACTER
SET utf8 COLLATE utf8_unicode_ci;
gitextract_il3ij_3v/ ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app/ │ ├── components/ │ │ └── Hash.php │ ├── config.sample.php │ ├── helpers.php │ ├── routes.php │ └── views/ │ └── index.php ├── composer.json ├── public/ │ ├── .htaccess │ ├── css/ │ │ └── app.css │ ├── index.php │ └── js/ │ └── index.js └── urls.sql
SYMBOL INDEX (8 symbols across 3 files)
FILE: app/components/Hash.php
class Hash (line 7) | class Hash
method __construct (line 11) | public function __construct(array $params)
method encode (line 20) | public function encode($id)
method decode (line 25) | public function decode($hash)
FILE: app/helpers.php
function avg (line 6) | function avg(array $data)
function url_modify (line 13) | function url_modify($url, $defaultScheme = 'http')
function real_remote_addr (line 30) | function real_remote_addr()
FILE: urls.sql
type `urls` (line 1) | CREATE TABLE `urls` (
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (18K chars).
[
{
"path": ".gitignore",
"chars": 46,
"preview": ".idea\r\n/app/config.php\r\nvendor\r\n/config.yaml\r\n"
},
{
"path": ".travis.yml",
"chars": 80,
"preview": "language: php\n\nphp:\n- '5.6'\n- '7.0'\n- '7.1'\n\nbefore_install:\n- composer install\n"
},
{
"path": "LICENSE",
"chars": 1083,
"preview": "MIT License\n\nCopyright (c) 2011 Mike Cao <mike@mikecao.com>\n\nPermission is hereby granted, free of charge, to any person"
},
{
"path": "README.md",
"chars": 1330,
"preview": "# Ourls\n\n[](https://packagist.org/packages/taka"
},
{
"path": "app/components/Hash.php",
"chars": 512,
"preview": "<?php\n\nnamespace app\\components;\n\nuse Hashids\\Hashids;\n\nclass Hash\n{\n public $hashids;\n\n public function __constru"
},
{
"path": "app/config.sample.php",
"chars": 1214,
"preview": "<?php\n\nreturn [\n 'debug' => true,\n 'base_url' => 'YourSiteUrl',\n 'hash' => [\n 'salt' => 'Some"
},
{
"path": "app/helpers.php",
"chars": 1595,
"preview": "<?php\n\nuse Etechnika\\IdnaConvert\\IdnaConvert;\n\nif (!function_exists('avg')) {\n function avg(array $data)\n {\n "
},
{
"path": "app/routes.php",
"chars": 3332,
"preview": "<?php\n\nFlight::route('/', function () {\n Flight::render('index.php');\n});\n\nFlight::route('/shorten', function () {\n "
},
{
"path": "app/views/index.php",
"chars": 3362,
"preview": "<!doctype html>\n<html class=\"no-js\">\n<head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"I"
},
{
"path": "composer.json",
"chars": 867,
"preview": "{\n \"name\": \"takashiki/ourls\",\n \"description\": \"A url shorten service system base on hash id\",\n \"keywords\": [\"ur"
},
{
"path": "public/.htaccess",
"chars": 125,
"preview": "RewriteEngine On\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteCond %{REQUEST_FILENAME} !-d\nRewriteRule ^(.*)$ index.php [Q"
},
{
"path": "public/css/app.css",
"chars": 111,
"preview": ".header {\n text-align: center;\n}\n\n.header h1 {\n font-size: 200%;\n color: #333;\n margin-top: 30px;\n}"
},
{
"path": "public/index.php",
"chars": 715,
"preview": "<?php\n\nrequire __DIR__.'/../vendor/autoload.php';\n\nrequire __DIR__.'/../app/helpers.php';\n\nrequire __DIR__.'/../app/rout"
},
{
"path": "public/js/index.js",
"chars": 1172,
"preview": "$('#shorten').click(function() {\n var raw_url = $('#url').val();\n if (validator.isURL(raw_url)) {\n var url "
},
{
"path": "urls.sql",
"chars": 503,
"preview": "CREATE TABLE `urls` (\n `id` INT (11) NOT NULL AUTO_INCREMENT,\n `sha1` CHAR (40) NOT NULL,\n `url` VARCHAR (255) "
}
]
About this extraction
This page contains the full source code of the takashiki/Ourls GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (15.7 KB), approximately 4.8k tokens, and a symbol index with 8 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.