This commit is contained in:
2024-07-17 17:16:01 +08:00
parent 74c5acc54f
commit f1ed9617d4
174 changed files with 29462 additions and 1 deletions

Submodule digital_doctor/vendor/wanghua/general-utility-tools-php deleted from 622c42b186

View File

@@ -0,0 +1 @@
ref: refs/heads/master

View File

@@ -0,0 +1 @@
622c42b186706c0d7354d04b8c957fe715d0c177

View File

@@ -0,0 +1,16 @@
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
[remote "origin"]
url = https://gitee.com/drop_drop/general_utility_tools_php.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[remote "composer"]
url = https://gitee.com/drop_drop/general_utility_tools_php.git
fetch = +refs/heads/*:refs/remotes/composer/*

View File

@@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -0,0 +1,15 @@
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:

View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".
# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}

View File

@@ -0,0 +1,174 @@
#!/usr/bin/perl
use strict;
use warnings;
use IPC::Open2;
# An example hook script to integrate Watchman
# (https://facebook.github.io/watchman/) with git to speed up detecting
# new and modified files.
#
# The hook is passed a version (currently 2) and last update token
# formatted as a string and outputs to stdout a new update token and
# all files that have been modified since the update token. Paths must
# be relative to the root of the working tree and separated by a single NUL.
#
# To enable this hook, rename this file to "query-watchman" and set
# 'git config core.fsmonitor .git/hooks/query-watchman'
#
my ($version, $last_update_token) = @ARGV;
# Uncomment for debugging
# print STDERR "$0 $version $last_update_token\n";
# Check the hook interface version
if ($version ne 2) {
die "Unsupported query-fsmonitor hook version '$version'.\n" .
"Falling back to scanning...\n";
}
my $git_work_tree = get_working_dir();
my $retry = 1;
my $json_pkg;
eval {
require JSON::XS;
$json_pkg = "JSON::XS";
1;
} or do {
require JSON::PP;
$json_pkg = "JSON::PP";
};
launch_watchman();
sub launch_watchman {
my $o = watchman_query();
if (is_work_tree_watched($o)) {
output_result($o->{clock}, @{$o->{files}});
}
}
sub output_result {
my ($clockid, @files) = @_;
# Uncomment for debugging watchman output
# open (my $fh, ">", ".git/watchman-output.out");
# binmode $fh, ":utf8";
# print $fh "$clockid\n@files\n";
# close $fh;
binmode STDOUT, ":utf8";
print $clockid;
print "\0";
local $, = "\0";
print @files;
}
sub watchman_clock {
my $response = qx/watchman clock "$git_work_tree"/;
die "Failed to get clock id on '$git_work_tree'.\n" .
"Falling back to scanning...\n" if $? != 0;
return $json_pkg->new->utf8->decode($response);
}
sub watchman_query {
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
or die "open2() failed: $!\n" .
"Falling back to scanning...\n";
# In the query expression below we're asking for names of files that
# changed since $last_update_token but not from the .git folder.
#
# To accomplish this, we're using the "since" generator to use the
# recency index to select candidate nodes and "fields" to limit the
# output to file names only. Then we're using the "expression" term to
# further constrain the results.
my $last_update_line = "";
if (substr($last_update_token, 0, 1) eq "c") {
$last_update_token = "\"$last_update_token\"";
$last_update_line = qq[\n"since": $last_update_token,];
}
my $query = <<" END";
["query", "$git_work_tree", {$last_update_line
"fields": ["name"],
"expression": ["not", ["dirname", ".git"]]
}]
END
# Uncomment for debugging the watchman query
# open (my $fh, ">", ".git/watchman-query.json");
# print $fh $query;
# close $fh;
print CHLD_IN $query;
close CHLD_IN;
my $response = do {local $/; <CHLD_OUT>};
# Uncomment for debugging the watch response
# open ($fh, ">", ".git/watchman-response.json");
# print $fh $response;
# close $fh;
die "Watchman: command returned no output.\n" .
"Falling back to scanning...\n" if $response eq "";
die "Watchman: command returned invalid output: $response\n" .
"Falling back to scanning...\n" unless $response =~ /^\{/;
return $json_pkg->new->utf8->decode($response);
}
sub is_work_tree_watched {
my ($output) = @_;
my $error = $output->{error};
if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
$retry--;
my $response = qx/watchman watch "$git_work_tree"/;
die "Failed to make watchman watch '$git_work_tree'.\n" .
"Falling back to scanning...\n" if $? != 0;
$output = $json_pkg->new->utf8->decode($response);
$error = $output->{error};
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
# Uncomment for debugging watchman output
# open (my $fh, ">", ".git/watchman-output.out");
# close $fh;
# Watchman will always return all files on the first query so
# return the fast "everything is dirty" flag to git and do the
# Watchman query just to get it over with now so we won't pay
# the cost in git to look up each individual file.
my $o = watchman_clock();
$error = $output->{error};
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
output_result($o->{clock}, ("/"));
$last_update_token = $o->{clock};
eval { launch_watchman() };
return 0;
}
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
return 1;
}
sub get_working_dir {
my $working_dir;
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
$working_dir = Win32::GetCwd();
$working_dir =~ tr/\\/\//;
} else {
require Cwd;
$working_dir = Cwd::cwd();
}
return $working_dir;
}

View File

@@ -0,0 +1,8 @@
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".
exec git update-server-info

View File

@@ -0,0 +1,14 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
precommit="$(git rev-parse --git-path hooks/pre-commit)"
test -x "$precommit" && exec "$precommit" ${1+"$@"}
:

View File

@@ -0,0 +1,49 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=$(git hash-object -t tree /dev/null)
fi
# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --type=bool hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff-index --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --

View File

@@ -0,0 +1,13 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git merge" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message to
# stderr if it wants to stop the merge commit.
#
# To enable this hook, rename this file to "pre-merge-commit".
. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
exec "$GIT_DIR/hooks/pre-commit"
:

View File

@@ -0,0 +1,53 @@
#!/bin/sh
# An example hook script to verify what is about to be pushed. Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed. If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
# <local ref> <local oid> <remote ref> <remote oid>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).
remote="$1"
url="$2"
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
while read local_ref local_oid remote_ref remote_oid
do
if test "$local_oid" = "$zero"
then
# Handle delete
:
else
if test "$remote_oid" = "$zero"
then
# New branch, examine all commits
range="$local_oid"
else
# Update to existing branch, examine new commits
range="$remote_oid..$local_oid"
fi
# Check for WIP commit
commit=$(git rev-list -n 1 --grep '^WIP' "$range")
if test -n "$commit"
then
echo >&2 "Found WIP commit in $local_ref, not pushing"
exit 1
fi
fi
done
exit 0

View File

@@ -0,0 +1,169 @@
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.
publish=next
basebranch="$1"
if test "$#" = 2
then
topic="refs/heads/$2"
else
topic=`git symbolic-ref HEAD` ||
exit 0 ;# we do not interrupt rebasing detached HEAD
fi
case "$topic" in
refs/heads/??/*)
;;
*)
exit 0 ;# we do not interrupt others.
;;
esac
# Now we are dealing with a topic branch being rebased
# on top of master. Is it OK to rebase it?
# Does the topic really exist?
git show-ref -q "$topic" || {
echo >&2 "No such branch $topic"
exit 1
}
# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
echo >&2 "$topic is fully merged to master; better remove it."
exit 1 ;# we could allow it, but there is no point.
fi
# Is topic ever merged to next? If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
not_in_topic=`git rev-list "^$topic" master`
if test -z "$not_in_topic"
then
echo >&2 "$topic is already up to date with master"
exit 1 ;# we could allow it, but there is no point.
else
exit 0
fi
else
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
/usr/bin/perl -e '
my $topic = $ARGV[0];
my $msg = "* $topic has commits already merged to public branch:\n";
my (%not_in_next) = map {
/^([0-9a-f]+) /;
($1 => 1);
} split(/\n/, $ARGV[1]);
for my $elem (map {
/^([0-9a-f]+) (.*)$/;
[$1 => $2];
} split(/\n/, $ARGV[2])) {
if (!exists $not_in_next{$elem->[0]}) {
if ($msg) {
print STDERR $msg;
undef $msg;
}
print STDERR " $elem->[1]\n";
}
}
' "$topic" "$not_in_next" "$not_in_master"
exit 1
fi
<<\DOC_END
This sample hook safeguards topic branches that have been
published from being rewound.
The workflow assumed here is:
* Once a topic branch forks from "master", "master" is never
merged into it again (either directly or indirectly).
* Once a topic branch is fully cooked and merged into "master",
it is deleted. If you need to build on top of it to correct
earlier mistakes, a new topic branch is created by forking at
the tip of the "master". This is not strictly necessary, but
it makes it easier to keep your history simple.
* Whenever you need to test or publish your changes to topic
branches, merge them into "next" branch.
The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.
With this workflow, you would want to know:
(1) ... if a topic branch has ever been merged to "next". Young
topic branches can have stupid mistakes you would rather
clean up before publishing, and things that have not been
merged into other branches can be easily rebased without
affecting other people. But once it is published, you would
not want to rewind it.
(2) ... if a topic branch has been fully merged to "master".
Then you can delete it. More importantly, you should not
build on top of it -- other people may already want to
change things related to the topic as patches against your
"master", so if you need further changes, it is better to
fork the topic (perhaps with the same name) afresh from the
tip of "master".
Let's look at this example:
o---o---o---o---o---o---o---o---o---o "next"
/ / / /
/ a---a---b A / /
/ / / /
/ / c---c---c---c B /
/ / / \ /
/ / / b---b C \ /
/ / / / \ /
---o---o---o---o---o---o---o---o---o---o---o "master"
A, B and C are topic branches.
* A has one fix since it was merged up to "next".
* B has finished. It has been fully merged up to "master" and "next",
and is ready to be deleted.
* C has not merged to "next" at all.
We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.
To compute (1):
git rev-list ^master ^topic next
git rev-list ^master next
if these match, topic has not merged in next at all.
To compute (2):
git rev-list master..topic
if this is empty, it is fully merged to "master".
DOC_END

View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to make use of push options.
# The example simply echoes all push options that start with 'echoback='
# and rejects all pushes when the "reject" push option is used.
#
# To enable this hook, rename this file to "pre-receive".
if test -n "$GIT_PUSH_OPTION_COUNT"
then
i=0
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
esac
i=$((i + 1))
done
fi

View File

@@ -0,0 +1,42 @@
#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source. The hook's purpose is to edit the commit
# message file. If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".
# This hook includes three examples. The first one removes the
# "# Please enter the commit message..." help message.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output. It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited. This is rarely a good idea.
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
# case "$COMMIT_SOURCE,$SHA1" in
# ,|template,)
# /usr/bin/perl -i.bak -pe '
# print "\n" . `git diff --cached --name-status -r`
# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
# *) ;;
# esac
# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
# if test -z "$COMMIT_SOURCE"
# then
# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
# fi

View File

@@ -0,0 +1,78 @@
#!/bin/sh
# An example hook script to update a checked-out tree on a git push.
#
# This hook is invoked by git-receive-pack(1) when it reacts to git
# push and updates reference(s) in its repository, and when the push
# tries to update the branch that is currently checked out and the
# receive.denyCurrentBranch configuration variable is set to
# updateInstead.
#
# By default, such a push is refused if the working tree and the index
# of the remote repository has any difference from the currently
# checked out commit; when both the working tree and the index match
# the current commit, they are updated to match the newly pushed tip
# of the branch. This hook is to be used to override the default
# behaviour; however the code below reimplements the default behaviour
# as a starting point for convenient modification.
#
# The hook receives the commit with which the tip of the current
# branch is going to be updated:
commit=$1
# It can exit with a non-zero status to refuse the push (when it does
# so, it must not modify the index or the working tree).
die () {
echo >&2 "$*"
exit 1
}
# Or it can make any necessary changes to the working tree and to the
# index to bring them to the desired state when the tip of the current
# branch is updated to the new commit, and exit with a zero status.
#
# For example, the hook can simply run git read-tree -u -m HEAD "$1"
# in order to emulate git fetch that is run in the reverse direction
# with git push, as the two-tree form of git read-tree -u -m is
# essentially the same as git switch or git checkout that switches
# branches while keeping the local changes in the working tree that do
# not interfere with the difference between the branches.
# The below is a more-or-less exact translation to shell of the C code
# for the default behaviour for git's push-to-checkout hook defined in
# the push_to_deploy() function in builtin/receive-pack.c.
#
# Note that the hook will be executed from the repository directory,
# not from the working tree, so if you want to perform operations on
# the working tree, you will have to adapt your code accordingly, e.g.
# by adding "cd .." or using relative paths.
if ! git update-index -q --ignore-submodules --refresh
then
die "Up-to-date check failed"
fi
if ! git diff-files --quiet --ignore-submodules --
then
die "Working directory has unstaged changes"
fi
# This is a rough translation of:
#
# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
if git cat-file -e HEAD 2>/dev/null
then
head=HEAD
else
head=$(git hash-object -t tree --stdin </dev/null)
fi
if ! git diff-index --quiet --cached --ignore-submodules $head --
then
die "Working directory has staged changes"
fi
if ! git read-tree -u -m "$commit"
then
die "Could not update working tree to new HEAD"
fi

View File

@@ -0,0 +1,77 @@
#!/bin/sh
# An example hook script to validate a patch (and/or patch series) before
# sending it via email.
#
# The hook should exit with non-zero status after issuing an appropriate
# message if it wants to prevent the email(s) from being sent.
#
# To enable this hook, rename this file to "sendemail-validate".
#
# By default, it will only check that the patch(es) can be applied on top of
# the default upstream branch without conflicts in a secondary worktree. After
# validation (successful or not) of the last patch of a series, the worktree
# will be deleted.
#
# The following config variables can be set to change the default remote and
# remote ref that are used to apply the patches against:
#
# sendemail.validateRemote (default: origin)
# sendemail.validateRemoteRef (default: HEAD)
#
# Replace the TODO placeholders with appropriate checks according to your
# needs.
validate_cover_letter () {
file="$1"
# TODO: Replace with appropriate checks (e.g. spell checking).
true
}
validate_patch () {
file="$1"
# Ensure that the patch applies without conflicts.
git am -3 "$file" || return
# TODO: Replace with appropriate checks for this patch
# (e.g. checkpatch.pl).
true
}
validate_series () {
# TODO: Replace with appropriate checks for the whole series
# (e.g. quick build, coding style checks, etc.).
true
}
# main -------------------------------------------------------------------------
if test "$GIT_SENDEMAIL_FILE_COUNTER" = 1
then
remote=$(git config --default origin --get sendemail.validateRemote) &&
ref=$(git config --default HEAD --get sendemail.validateRemoteRef) &&
worktree=$(mktemp --tmpdir -d sendemail-validate.XXXXXXX) &&
git worktree add -fd --checkout "$worktree" "refs/remotes/$remote/$ref" &&
git config --replace-all sendemail.validateWorktree "$worktree"
else
worktree=$(git config --get sendemail.validateWorktree)
fi || {
echo "sendemail-validate: error: failed to prepare worktree" >&2
exit 1
}
unset GIT_DIR GIT_WORK_TREE
cd "$worktree" &&
if grep -q "^diff --git " "$1"
then
validate_patch "$1"
else
validate_cover_letter "$1"
fi &&
if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL"
then
git config --unset-all sendemail.validateWorktree &&
trap 'git worktree remove -ff "$worktree"' EXIT &&
validate_series
fi

View File

@@ -0,0 +1,128 @@
#!/bin/sh
#
# An example hook script to block unannotated tags from entering.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# To enable this hook, rename this file to "update".
#
# Config
# ------
# hooks.allowunannotated
# This boolean sets whether unannotated tags will be allowed into the
# repository. By default they won't be.
# hooks.allowdeletetag
# This boolean sets whether deleting tags will be allowed in the
# repository. By default they won't be.
# hooks.allowmodifytag
# This boolean sets whether a tag may be modified after creation. By default
# it won't be.
# hooks.allowdeletebranch
# This boolean sets whether deleting branches will be allowed in the
# repository. By default they won't be.
# hooks.denycreatebranch
# This boolean sets whether remotely creating branches will be denied
# in the repository. By default this is allowed.
#
# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"
# --- Safety check
if [ -z "$GIT_DIR" ]; then
echo "Don't run this script from the command line." >&2
echo " (if you want, you could supply GIT_DIR then run" >&2
echo " $0 <ref> <oldrev> <newrev>)" >&2
exit 1
fi
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
exit 1
fi
# --- Config
allowunannotated=$(git config --type=bool hooks.allowunannotated)
allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
# check for no description
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
case "$projectdesc" in
"Unnamed repository"* | "")
echo "*** Project description file hasn't been set" >&2
exit 1
;;
esac
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
newrev_type=$(git cat-file -t $newrev)
fi
case "$refname","$newrev_type" in
refs/tags/*,commit)
# un-annotated tag
short_refname=${refname##refs/tags/}
if [ "$allowunannotated" != "true" ]; then
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
exit 1
fi
;;
refs/tags/*,delete)
# delete tag
if [ "$allowdeletetag" != "true" ]; then
echo "*** Deleting a tag is not allowed in this repository" >&2
exit 1
fi
;;
refs/tags/*,tag)
# annotated tag
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
then
echo "*** Tag '$refname' already exists." >&2
echo "*** Modifying a tag is not allowed in this repository." >&2
exit 1
fi
;;
refs/heads/*,commit)
# branch
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
echo "*** Creating a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/heads/*,delete)
# delete branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/remotes/*,commit)
# tracking branch
;;
refs/remotes/*,delete)
# delete tracking branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
exit 1
fi
;;
*)
# Anything else (is there anything else?)
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
exit 1
;;
esac
# --- Finished
exit 0

View File

@@ -0,0 +1,6 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

View File

@@ -0,0 +1,4 @@
622c42b186706c0d7354d04b8c957fe715d0c177 refs/heads/master
622c42b186706c0d7354d04b8c957fe715d0c177 refs/remotes/origin/HEAD
58562ddc524fd11b529855134aa6227d5a537d70 refs/remotes/origin/dev
622c42b186706c0d7354d04b8c957fe715d0c177 refs/remotes/origin/master

View File

@@ -0,0 +1,3 @@
0000000000000000000000000000000000000000 622c42b186706c0d7354d04b8c957fe715d0c177 wh <382379437@qq.com> 1721188454 +0800 clone: from C:/Users/lenovo/AppData/Local/Composer/vcs/https---gitee.com-drop-drop-general-utility-tools-php.git/
622c42b186706c0d7354d04b8c957fe715d0c177 622c42b186706c0d7354d04b8c957fe715d0c177 wh <382379437@qq.com> 1721188455 +0800 checkout: moving from master to master
622c42b186706c0d7354d04b8c957fe715d0c177 622c42b186706c0d7354d04b8c957fe715d0c177 wh <382379437@qq.com> 1721188455 +0800 reset: moving to 622c42b186706c0d7354d04b8c957fe715d0c177

View File

@@ -0,0 +1 @@
0000000000000000000000000000000000000000 622c42b186706c0d7354d04b8c957fe715d0c177 wh <382379437@qq.com> 1721188454 +0800 clone: from C:/Users/lenovo/AppData/Local/Composer/vcs/https---gitee.com-drop-drop-general-utility-tools-php.git/

View File

@@ -0,0 +1 @@
0000000000000000000000000000000000000000 622c42b186706c0d7354d04b8c957fe715d0c177 wh <382379437@qq.com> 1721188454 +0800 clone: from C:/Users/lenovo/AppData/Local/Composer/vcs/https---gitee.com-drop-drop-general-utility-tools-php.git/

View File

@@ -0,0 +1,2 @@
P pack-ef866fac58cb9abd740db8b68f4b3caac8981535.pack

View File

@@ -0,0 +1,3 @@
# pack-refs with: peeled fully-peeled sorted
58562ddc524fd11b529855134aa6227d5a537d70 refs/remotes/origin/dev
622c42b186706c0d7354d04b8c957fe715d0c177 refs/remotes/origin/master

View File

@@ -0,0 +1 @@
622c42b186706c0d7354d04b8c957fe715d0c177

View File

@@ -0,0 +1 @@
ref: refs/remotes/origin/master

View File

@@ -0,0 +1,6 @@
.idea
.git
/.git
/.idea
/composer.lock

View File

@@ -0,0 +1,37 @@
# general_utility_tools_php
#### Description
php常用工具箱
general utility tool for php
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

View File

@@ -0,0 +1,142 @@
# general_utility_tools_php
#### 介绍
[PHP常用通用工具库]
一、常用验证类库
1. 验证手机号是否正确
2. 验证邮箱
3. 验证身份证号码
4. 是否字母或数字
5. 是否是小数格式
6. 更多功能请参考代码
二、PHP日期处理工具
1. 日期加上N年、月、日、时、分、秒得到计算后的时间
2. 两个日期相减得到天、时、分、秒(没有两个日期相加一说)
3. 更多更完善功能敬请期待......
三、更多类库参考本类库目录
1. 每个目录都有使用说明.
#### 软件架构
每一种工具独立为一个功能类库。
#### 软件要求
thinkphp5.0+PHP7.1+
#### 安装教程
composer require wanghua/general-utility-tools-php dev-master
###### 注意如果总是安装失败可切换国内或者国外源如果是找不到版本则加上dev-master尝试
###### 注意如果总是安装失败可卸载此包重新安装卸载指令composer remove wanghua/general-utility-tools-php
###### 注意:如果总是不能提交 vendor下面的一个文件夹(或文件)请参考这篇文章解决https://blog.csdn.net/qq_15941409/article/details/113184021
### 一、常用验证类库使用说明
##### 初始化
//静态方法无需实例化直接调用
##### 验证参数是否是小数格式
$str = '1.223';
var_dump(Validate::is_float_number($str));
##### 验证参数是否字母或数字
$str = 'qwer#199';
var_dump(Validate::is_letter_or_number($str));
###### 注: 更多功能请参考源码
### 二、PHP日期处理工具使用说明
##### 初始化
$date = (new Date());
$date->date_format = 'Y-m-d H:i:s';//设置格式默认Y-m-d H:i:s
##### 日期类型参数(第2个参数)可选项:
//分、时、天、周、月、年
protected $data_type = [
'm' =>'minute',//分钟
'minute' =>'minute',//分钟
'h' =>'hour',//小时
'hour' =>'hour',//小时
'd' =>'day',//天
'day' =>'day',//天
'w' =>'week',//周
'week' =>'week',//周
'M' =>'month',//月
'month' =>'month',//月
'y' =>'year',//年
'year' =>'year',//年
];
##### 在当前时间基础上加3天第三个参数不传默认使用当前时间
$res = $date->addTime(3, 'd');//支持单词和字母例如d表示天day也表示天
dump($res);
##### 在当前时间基础上加1小时第三个参数不传默认使用当前时间
$res = $date->addTime(1, 'h');//支持单词和字母例如h表示小时hour也表示小时
dump($res);
##### 在指定时间基础上加1小时
$res = $date->addTime(1, 'h', strtotime('2010-10-01 12:00:10'));
dump($res);
##### 在指定时间基础上减1个月 注意月是大写M字母分钟是小写m字母
$res = $date->reduceTime(1, 'M', strtotime('2010-10-01 12:00:10'));
dump($res);
##### 在指定时间基础上减20分钟 注意分钟是小写m字母
$res = $date->reduceTime(20, 'm', strtotime('2010-10-01 12:00:10'));
dump($res);
##### 在指定时间基础上减1年
$res = $date->reduceTime(1, 'y', strtotime('2010-10-01 12:00:10'));
dump($res);
### 日期时间相减
//时间相减返回的时间类型 秒、分、时、天 默认返回秒
protected $time_type = [
's' =>1,//秒
'second' =>1,//秒
'm' =>60,//分钟
'minute' =>60,//分钟
'h' =>3600,//小时
'hour' =>3600,//小时
'd' =>86400,//天
'day' =>86400,//天
];
$start_time = '2010-05-01 12:30:00';
$end_time = '2010-10-28 12:30:00';
//结束时间减去开始时间得到秒数 (返回类型参考上方配置)
$res = $date->timeReduceTime($end_time, $start_time);
dump($res);
//结束时间减去开始时间得到天数 (返回类型参考上方配置)
$res = $date->timeReduceTime($end_time, $start_time, 'day');
dump($res);
###### 注: 更多功能请参考源码
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

View File

@@ -0,0 +1,25 @@
{
"type": "library",
"name": "wanghua/general-utility-tools-php",
"homepage": "https://gitee.com/drop_drop/general_utility_tools_php.git",
"description": "general utility tools for thinkPHP",
"license": "Apache-2.0",
"version":"1.0.1",
"keywords": ["general-utility-tools","php tool","common tool"],
"authors": [
{
"name": "wanghua",
"email": "wanghua@qq.com",
"homepage": "https://blog.csdn.net/qq_15941409"
}
],
"minimum-stability": "stable",
"require": {
"ext-json": "*"
},
"autoload": {
"psr-4": {
"wanghua\\general_utility_tools_php\\": "src/"
}
}
}

View File

@@ -0,0 +1,396 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/10/27} {11:43}
*/
namespace wanghua\general_utility_tools_php;
/**
* 日期时间处理类
* Class Date
* @package libraries
*/
class Date
{
//时间默认返回格式
public $date_format = 'Y-m-d H:i:s';
//分、时、天、周、月、年
protected $data_type = [
'm' =>'minute',//分钟
'minute' =>'minute',//分钟
'h' =>'hour',//小时
'hour' =>'hour',//小时
'd' =>'day',//天
'day' =>'day',//天
'w' =>'week',//周
'week' =>'week',//周
'M' =>'month',//月
'month' =>'month',//月
'y' =>'year',//年
'year' =>'year',//年
];
//秒、分、时、天
protected $time_type = [
's' =>1,//秒
'second' =>1,//秒
'm' =>60,//分钟
'minute' =>60,//分钟
'h' =>3600,//小时
'hour' =>3600,//小时
'd' =>86400,//天
'day' =>86400,//天
];
/**
* desc日期时间加N
*
* 【注意】
* 请在计算月时使用$date->date_format = 'Y-m'防止跳月份
* 例如2020-03-31月基础上加一个月在默认$date->date_format='Y-m-d H:i:s'时会跳到2020-05-01
*
* authorwh
* @param int $times 相加的时间数量 整型
* @param string $date_type 要相加的时间类型 可选值m 分钟h 小时d 天w 周M 月y 年
* @param int $default_time 时间戳,默认当前时间
* @return false|string 返回$this->date_format格式可根据需要设定格式
*/
function addTime(int $times, string $date_type, int $default_time=0){
return date($this->date_format, strtotime("+{$times} {$this->data_type[$date_type]}", $default_time?$default_time:time()));
}
/**
* desc日期时间减N
*
* 【注意】
* 请在计算月时使用$date->date_format = 'Y-m'防止跳月份
* 例如2020-03-31月基础上加一个月在默认$date->date_format='Y-m-d H:i:s'时会跳到2020-05-01
*
* authorwh
* @param int $times 减去的时间数量 整型
* @param string $date_type 要相减的时间类型 可选值m 分钟h 小时d 天w 周M 月y 年
* @param int $default_time 时间戳,默认当前时间
* @return false|string 返回$this->date_format格式可根据需要设定格式
*/
function reduceTime(int $times, string $date_type, int $default_time=0){
return date($this->date_format, strtotime("-{$times} {$this->data_type[$date_type]}", $default_time?$default_time:time()));
}
/**
* desc日期时间相减通常结束时间大于开始时间
* authorwh
* @param string $end_time 结束时间
* @param string $start_time 开始时间
* @param string $return_type 日期时间相减后得到的时间类型可能是小数。可选值s 秒m 分钟h 小时d
* @return float|int 返回计算后的天、时、分、秒数
*/
function timeReduceTime(string $end_time, string $start_time, string $return_type='s'){
return (strtotime($end_time) - strtotime($start_time)) / $this->time_type[$return_type];
}
/**
* desc日期减日期, 返回月数或年数
*
* authorwh
* @param string $start_time 开始时间
* @param string $end_time 结束时间
* @param string $return_type M 返回一共有多少个月y 返回有多少个年
* @return float|int
*/
function dateCutDate(string $start_time, string $end_time, string $return_type='M'){
$e = date_create($end_time);
$s = date_create($start_time);
$diff = date_diff($e, $s);
//计算月份
if ($diff->y > 0) {
$m = $diff->y * 12 + $diff->m;
}else{
$m = $diff->m;
}
return $return_type=='M'?$m:$diff->y;
}
/**
* desc日期相减得到月数
* 注意:不是计算的时间戳,而是计算的月份差值
* authorwh
* @param string $start_time
* @param string $end_time
* @return false|float|int|string
*/
function dateCutMonth(string $start_time, string $end_time){
$start_y = date('Y', strtotime($start_time));
$end_y = date('Y', strtotime($end_time));
$start_m = date('m', strtotime($start_time));
$end_m = date('m', strtotime($end_time));
$ym = ($end_y-$start_y) * 12;
return $ym - $start_m + $end_m;
}
/**
* desc日期相减得到年数
* 注意:不是计算的时间戳,而是计算的年份差值
* authorwh
* @param string $start_time
* @param string $end_time
* @return false|float|int|string
*/
function dateCutYear(string $start_time, string $end_time){
$start_y = date('Y', strtotime($start_time));
$end_y = date('Y', strtotime($end_time));
return ($end_y-$start_y) * 12;
}
/**
* desc返回年中第几天
* authorwh
* @param string $month 指定月份
* @param string $day 指定日
* @param string $year 指定年份
* @return false|string
*/
function get_day_Year(string $month, string $day, string $year){
return date('z',mktime(0,0,0,$month,$day,$year));
}
/**
* desc今天的开始时间
* authorwh
* @return false|string
*/
function beginToday(){
return date('Y-m-d').' 00:00:00';
}
/**
* desc今天的结束时间
* authorwh
* @return false|string
*/
function endToday(){
return date('Y-m-d').' 23:59:59';
}
/**
* desc昨天的开始时间
* authorwh
* @return string
*/
function beginYesterday(){
return date('Y-m-d', strtotime('-1 day')).' 00:00:00';
}
/**
* desc昨天的结束时间
* authorwh
* @return string
*/
function endYesterday(){
return date('Y-m-d', strtotime('-1 day')).' 23:59:59';
}
/**
* desc前天的开始时间
* authorwh
* @return string
*/
function beginBeforeYesterday(){
return date('Y-m-d', strtotime('-2 day')).' 00:00:00';
}
/**
* desc前天的结束时间
* authorwh
* @return string
*/
function endBeforeYesterday(){
return date('Y-m-d', strtotime('-2 day')).' 23:59:59';
}
/**
* 本周的开始日期
*
* @param bool $His 是否展示时分秒 默认true
*
* @return false|string
*/
function beginWeek($His = true)
{
//此代码会跳周、月、年
//$timestamp = mktime(0, 0, 0, date('m'), date('d') - date('w') + 1, date('Y'));
//return $His ? date('Y-m-d H:i:s', $timestamp) : date('Y-m-d', $timestamp);
//改进
//$first =1 表示每周星期一为开始日期 0表示每周日为开始日期
$first=1;
$sdefaultDate = date("Y-m-d");
//获取当前周的第几天 周日是 0 周一到周六是 1 - 6
$w=date('w',strtotime($sdefaultDate));
//是否展示时分秒
$week_start = date('Y-m-d',strtotime("$sdefaultDate -".($w ? $w - $first : 6).' days'));
//是否展示时分秒
if($His){
//$week_start
return $week_start.' 00:00:00';
}
//$week_start
return $week_start;
}
/**
* 本周的结束日期
*
* @param bool $His 是否展示时分秒 默认true
*
* @return false|string
*/
function endWeek($His = true)
{
//此代码会跳周、月、年
//$timestamp = mktime(23, 59, 59, date('m'), date('d') - date('w') + 7, date('Y'));
//return $His ? date('Y-m-d H:i:s', $timestamp) : date('Y-m-d', $timestamp);
//改进
//$first =1 表示每周星期一为开始日期 0表示每周日为开始日期
$first=1;
$sdefaultDate = date("Y-m-d");
//获取当前周的第几天 周日是 0 周一到周六是 1 - 6
$w=date('w',strtotime($sdefaultDate));
//是否展示时分秒
$week_start = date('Y-m-d',strtotime("$sdefaultDate -".($w ? $w - $first : 6).' days'));
$week_end=date('Y-m-d',strtotime("$week_start +6 days"));
//是否展示时分秒
return $His?$week_end.' 23:59:59':$week_end;
}
/**
* desc上周开始
* authorwh
* @return false|string
*/
function beginBeforeWeek(){
$week = $this->beginWeek();
$this->date_format = 'Y-m-d';
return $this->reduceTime(7,'d',strtotime($week)) . ' 00:00:00';
//此代码会跳周、月、年
//return date('Y-m-d', strtotime('-1 monday', time())).' 00:00:00';//上周一,无论今天几号,-1 monday为上一个有效周未
}
/**
* desc上周结束
* authorwh
* @return false|string
*/
function endBeforeWeek(){
$week = $this->beginWeek();
$this->date_format = 'Y-m-d';
return $this->reduceTime(1,'d',strtotime($week)) . ' 23:59:59';
//此代码会跳周、月、年
//return date('Y-m-d', strtotime('-1 sunday', time())).' 23:59:59'; //上一个有效周日,同样适用于其它星期
}
/**
* 本月的开始日期
*
* @param bool $His 是否展示时分秒 默认true
*
* @return false|string
*/
function beginMonth($His = true)
{
return $His ? date('Y-m-').'01 00:00:00' : date('Y-m-').'01';
}
/**
* 本月的结束日期
*
* @param bool $His 是否展示时分秒 默认true
*
* @return false|string
*/
function endMonth($His = true)
{
$timestamp = mktime(23, 59, 59, date('m'), date('t'), date('Y'));
return $His ? date('Y-m-d H:i:s', $timestamp) : date('Y-m-d', $timestamp);
}
/**
* desc上月开始时间上月一日
* authorwh
* @return false|string
*/
function beginBeforeMonth(){
return date('Y-m-d', strtotime('-1 month', strtotime(date('Y-m', time()) . '-01 00:00:00'))).' 00:00:00'; //本月一日直接strtotime上减一个月
}
/**
* desc上月结束时间上月最后一日
* authorwh
* @return false|string
*/
function endBeforeMonth(){
return date('Y-m-d', strtotime(date('Y-m', time()) . '-01 00:00:00') - 86400).' 23:59:59'; //本月一日减一天即是上月最后一日
}
/**
* desc七天(一周)以内
* authorwh
* @return false|string
*/
function innerWeekDay(){
$time = time();
//当前时间减去30天
$second = $time - 7 * 86400;
return date('Y-m-d', $second);
}
/**
* desc一月以内
* authorwh
* @return false|string
*/
function innerMonth(){
$time = time();
//当前时间减去30天
$second = $time - 30 * 86400;
return date('Y-m-d', $second);
}
/**
* desc一年以内
* authorwh
* @return false|string
*/
function innerYear(){
$time = time();
//当前时间减去30天
$second = $time - 365 * 86400;
return date('Y-m-d', $second);
}
/**
* 几年的开始日期
*
* @param bool $His 是否展示时分秒 默认true
*
* @return false|string
*/
function beginYear($His = true)
{
$timestamp = date('Y');
return $His ? $timestamp.'-01-01 00:00:00' : $timestamp.'-01-01';
}
/**
* 几年的结束日期
*
* @param bool $His 是否展示时分秒 默认true
*
* @return false|string
*/
function endYear($His = true)
{
$timestamp = date('Y');
return $His ? $timestamp.'-12-31 23:59:59' : $timestamp.'-12-31';
}
}

View File

@@ -0,0 +1,125 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/5/1} {21:28}
*/
namespace wanghua\general_utility_tools_php;
use think\Db;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* thinkphp5模型快捷方法库
* Class Mmodel
* @package wanghua\general_utility_tools_php
*/
class Mmodel
{
/**
* desc存在则更新否则插入
* authorwh
* @param string $table 表名
* @param array $where 更新条件(只在更新时有效)
* @param array $data 更新或写入数据
* @return int 数据主键id
*/
static function existsUpdateInsert($table,$where,$data){
if(empty($where)){
return Db::table($table)->insertGetId($data);
}
$res = Db::table($table)
->where($where)
->find();
if($res){
Db::table($table)
->where($where)
->data($data)
->update();
return $res['id'];
}
return Db::table($table)->insertGetId($data);
}
/**
* desc捕获式函数块
*
* 无事务处理
*
* authorwh
* @param $fn
*/
static function catch($fn){
try{
Tools::log_to_write_txt(['input'=>input()]);
return $fn();
}catch (\Exception $e){
Tools::error_txt_log($e);
return Tools::set_fail('操作失败.',$e->getMessage());
}
}
/**
* desc捕获式函数块
*
* 无事务处理
*
* authorwh
* @param $fn
*/
static function catchJson($fn){
try{
Tools::log_to_write_txt(['input'=>input()]);
$res = $fn();
return json($res);
}catch (\Exception $e){
Tools::error_txt_log($e);
return json(Tools::set_fail('操作失败.',$e->getMessage()));
}
}
/**
* desc捕获式函数块
*
* 自动事务处理
*
* authorwh
* @param $fn
*/
static function catchTrans($fn){
Db::startTrans();
try{
Tools::log_to_write_txt(['input'=>input()]);
$res = $fn();
Db::commit();
return $res;
}catch (\Exception $e){
Db::rollback();
Tools::error_txt_log($e);
return Tools::set_fail('操作失败.',$e->getMessage());
}
}
/**
* desc捕获式函数块
*
* 自动事务处理
*
* authorwh
* @param $fn
*/
static function catchTransJson($fn){
Db::startTrans();
try{
Tools::log_to_write_txt(['input'=>input()]);
$res = $fn();
Db::commit();
return json($res);
}catch (\Exception $e){
Db::rollback();
Tools::error_txt_log($e);
return json(Tools::set_fail('操作失败.',$e->getMessage()));
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,50 @@
<?php
/*
* description
* authorwh
* email
* createTime{2023/12/31} {21:56}
*/
namespace wanghua\general_utility_tools_php;
use app\common\model\TabConf;
use think\Db;
/**
* 系统杂项配置
*
* 读取fa_sundry_config表中的配置
*
* Class SundryConfig
* @package wanghua\general_utility_tools_php
*/
class SundryConfig
{
private static $tablename = 'fa_zc_sundry_config';
/**
* desc获取配置的值设置配置的值
*
* 默认缓存值
*
* authorwh
* @param string $key
* @param string $val
* @return float|mixed|string
*/
static function val(string $key,string $val=''){
$tabname = self::$tablename;
if($val){
$obj = Db::table($tabname);
$obj->where('key',$key);
$obj->data(['val'=>$val]);
return $obj->update();
}
$obj = Db::table($tabname);
return $obj->where(['key'=>$key])
->cache($tabname.'_'.$key,0,$tabname)
->value('val');
}
}

View File

@@ -0,0 +1,360 @@
<?php
/*
* description
* authorwh
* email
* createTime{2019/11/1} {15:09}
*/
namespace wanghua\general_utility_tools_php;
class Validate
{
/**
* desc验证手机号是否正确
* authorwh
* @param $m
* @return bool
*/
static function is_mobile($m) {
return preg_match('/^1[3,4,5,6,7,8,9]{1}\d{9}$/', $m) ? true : false;
}
/**
* 验证邮箱
* authorwh
* @param $email
* @return bool
*/
static function is_email($email){
return preg_match("/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,})$/",$email)?true:false;
}
/**
* [is_IDCard 验证身份证号码]
* @Author
* @Date 2019-11-26
* @param [string] $string [身份证号]
* @return boolean [description]
*/
static function is_IDCard($string){
return preg_match("/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/", $string)?true:false;
}
/**
* [is_money_times 验证金额是否是100的整数倍]
* @Author
* @Date 2019-12-12
* @param [type] $money [金额]
* @return boolean [description]
*/
static function is_money_times($money){
if(!is_numeric($money) || $money <= 0){
return false;
}
return $money%100===0;
}
/**
* desc是否是金额
* authorwh
* @param $money
* @return bool
*/
static function is_money($money){
return (preg_match('/^[0-9]+.[0-9]+$/', $money) || preg_match('/^\d+$/', $money))?true:false;
}
/**
* desc是否是金额并且最多只有两位小数
*
* 严格金额格式校验
*
* authorwh
* @param $money
* @return bool
*/
static function is_money_double($money){
$r = (preg_match('/^[0-9]+.[0-9]+$/', $money) || preg_match('/^\d+$/', $money))?true:false;
$exp = explode('.',$money);
if(strpos($money,'.') && preg_match('/^[0-9]+.[0-9]+$/', $money) && strlen($exp[1])>2){
//返回false表示非严格的2位数金额
return false;
}
return $r;
}
/**
* desc是否是小数格式
* authorwh
* @param string $num
* @return bool
*/
static function is_float_number(string $num){
return preg_match('/^[0-9]+.[0-9]+$/', $num)?true:false;
}
/**
* desc是否是数字
* authorwh
* @param string $num
* @return bool
*/
static function is_number(string $num){
return preg_match('/^\d+$/', $num)?true:false;
}
/**
* desc是否是字母
* author wh
* @param string $str
* @return bool
*/
static function is_letter(string $str){
return preg_match('/^[a-zA-Z]+$/', $str) ? true : false;
}
/**
* desc是否字母或数字
* authorwh
* @param string $str
* @return bool
*/
static function is_letter_or_number(string $str){
return preg_match('/^[a-zA-Z|0-9]+$/', $str) ? true : false;
}
/**
* desc验证数组是否存在空值
* 仅针对基本数据类型
* 仅针对一维数组
* authorwh
* @param $array
* @return bool
*/
static function check_array_val_empty($array){
$is_empty = false;
foreach ($array as $value){
if(($value!==0 && $value !=='0') && empty($value)){
$is_empty = true;
break;
}
if(is_int($value) && empty(1*$value)){
$is_empty = true;
break;
}
}
return $is_empty;
}
/**
* desc是否是url
* authorwh
* @param $v
* @return bool
*/
static function is_url($v){
$pattern="#(http|https)://(.*\.)?.*\..*#i";
return preg_match($pattern,$v)?true:false;
}
/**
* [是否全部大写]
*
* @param $str
* @return bool
* @example
* @see
* @link
*/
static function is_upper($str){
return preg_match('/^[A-Z]+$/', $str)?true:false;
}
/**
* [是否全部小写]
*
* @param $str
* @return bool
* @example
* @see
* @link
*/
static function is_lower($str){
return preg_match('/^[a-z]+$/', $str)?true:false;
}
/**
* desc验证是微信内还是微信外
*
* 是否在微信内,是否在微信中
*
* authorwh
* @return bool
*/
static function is_weixin(){
return strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false;
}
/**
* desc是否包含特殊字符
* authorwh
* @param string $str
* @return bool
*/
static function is_special_character(string $str){
$res = preg_match ( '/[\Q~!@#$%^&*()+-_=.:?<>\E]/', $str );
return $res ? true : false;
}
/**
* desc验证字符串是否全部是中文
*
* 返回 true表示全部是中文false表示部分是中文或没有中文
*
* authorwh
* @param string $str
* @return bool
*/
static function is_all_chinese(string $str){
return preg_match("/^[\x{4e00}-\x{9fa5}]+$/u",$str)?true:false;
}
/**
* desc验证两个浮点数值是否相等
*
* 例如:
* $num1 = 0.1;
$num2 = 0.7;
$res = $num1 + $num2;
var_dump($res);
var_dump($res == 0.8);//false 不相等
var_dump(intval(strval($res)) == intval(strval(0.8)));//true 相等
*
* authorwh
* @param $float_num1
* @param $float_num2
* @return bool
*/
static function is_equal_num_val($float_num1, $float_num2){
return intval(strval($float_num1)) == intval(strval($float_num2));
}
/**
* desc是否包含给定的主域名
* authorwh
* @param $domain
* @param $main_domain
* @return bool
*/
static function is_main_domain($domain,$main_domain){
$exp_arr = explode('.',$domain);
$str = $exp_arr[count($exp_arr)-2] .'.'. $exp_arr[count($exp_arr)-1];
return $str==$main_domain;
}
/**
* desc国内国外IP校验校验IP来源
*
* authorwh
* @param $ip
* @return bool
*/
static function validate_ip($ip) {
$chinaStart = ip2long('1.0.1.0'); // 中国大陆起始IP
$chinaEnd = ip2long('254.254.254.254'); // 中国大陆结束IP
$hongKongStart = ip2long('8.36.0.0'); // 香港起始IP
$hongKongEnd = ip2long('8.37.255.255'); // 香港结束IP
$taiwanStart = ip2long('192.168.0.0'); // 台湾起始IP
$taiwanEnd = ip2long('192.168.255.255'); // 台湾结束IP
$ipLong = ip2long($ip);
if ($ipLong >= $chinaStart && $ipLong <= $chinaEnd ||
$ipLong >= $hongKongStart && $ipLong <= $hongKongEnd ||
$ipLong >= $taiwanStart && $ipLong <= $taiwanEnd) {
return true; // IP属于国内
} else {
return false; // IP不属于国内
}
}
/**
* 是否移动端访问访问
*
* @return bool
*/
static function is_mobile_client()
{
// 如果有HTTP_X_WAP_PROFILE则一定是移动设备
if (isset($_SERVER['HTTP_X_WAP_PROFILE'])) {
return true;
}
//如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息
if (isset($_SERVER['HTTP_VIA'])) {
//找不到为flase,否则为true
return stristr($_SERVER['HTTP_VIA'], "wap") ? true : false;
}
//判断手机发送的客户端标志
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$clientkeywords = [
'nokia', 'sony', 'ericsson', 'mot', 'samsung', 'htc', 'sgh', 'lg', 'sharp',
'sie-', 'philips', 'panasonic', 'alcatel', 'lenovo', 'iphone', 'ipod', 'blackberry', 'meizu',
'android', 'netfront', 'symbian', 'ucweb', 'windowsce', 'palm', 'operamini', 'operamobi',
'openwave', 'nexusone', 'cldc', 'midp', 'wap', 'mobile','alipay'
];
// 从HTTP_USER_AGENT中查找手机浏览器的关键字
if (preg_match("/(".implode('|', $clientkeywords).")/i", strtolower($_SERVER['HTTP_USER_AGENT']))) {
return true;
}
}
//协议法,因为有可能不准确,放到最后判断
if (isset($_SERVER['HTTP_ACCEPT'])) {
// 如果只支持wml并且不支持html那一定是移动设备
// 如果支持wml和html但是wml在html之前则是移动设备
if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html')))) {
return true;
}
}
return false;
}
/**
* 判断是否微信内置浏览器访问
* @return bool
*/
static function is_wx_client()
{
return strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false;
}
/**
* 判断是否支付宝内置浏览器访问
* @return bool
*/
static function is_ali_client()
{
return strpos($_SERVER['HTTP_USER_AGENT'], 'Alipay') !== false;
}
}

View File

@@ -0,0 +1,160 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/6/28} {14:36}
*/
namespace wanghua\general_utility_tools_php\alibaba;
use wanghua\general_utility_tools_php\http\Curl;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* 授权
*
* 注意:
* 分销自研自用版不需要授权直接使用永久accessToken
* 请把当前账号添加为应用的日常测试账号
*
* Class Auth
* @package app\index\logic\alibaba
*/
class AlibabaAuth
{
public $base_url = '';//不同业务线,授权地址不一样
public $config = [];
public function __construct($config)
{
$this->config = $config;
}
function trusteeshipAuth(){
//待开发
}
/**
* desc授权分为托管式授权和WEB端授权这里是WEB端授权后去code临时凭证
*
* authorwh
* @param string $appKey app注册时分配给app的唯一标识
* @param string $redirect_uri app的入口地址授权临时令牌会以queryString的形式跟在该url后返回。注意参数中回调地址的域名必须与app注册时填写的回调地址的域名匹配。
* @param string $site site参数标识当前授权的站点直接填写1688
* @param string $state 可选app自定义参数回跳到redirect_uri时会原样返回
* return 用户输入用户名密码并确认授权返回临时授权码code给app具体返回方式参照redirect_uri说明
*/
function webAppAuth($redirect_uri,$site='1688',$state=''){
$appKey = $this->config['appkey'];
$url = "https://auth.1688.com/oauth/authorize?client_id={$appKey}&site={$site}&redirect_uri={$redirect_uri}&state={$state}";
//$res = Curl::curl_post($url,[]);
return "<script>location.href='{$url}'</script>";
}
/**
* desc使用code获取令牌【code有效期2分钟
*
* 注此接口必须使用POST方法提交必须使用https
getToken接口参数说明
a) grant_type为授权类型使用authorization_code即可
b) need_refresh_token为是否需要返回refresh_token如果返回了refresh_token原来获取的refresh_token也不会失效除非超过半年有效期
c) client_id为app唯一标识即appKey
d) client_secret为app密钥
e) redirect_uri为app入口地址
f) code为授权完成后返回的一次性令牌
g) 调用getToken接口不需要签名
如果超过code有效期2分钟或者已经使用code获取了一次令牌code都将失效需要返回第二步重新获取code
* authorwh
*/
function getTokenByCode($code,$redirect_uri){
$appkey = $this->config['appkey'];
$secret = $this->config['appsecret'];
$url = "https://gw.open.1688.com/openapi/";
$urlPath = "http/1/system.oauth2/getToken/".$appkey;
$paramstr = "?grant_type=authorization_code&need_refresh_token=true&client_id={$appkey}&client_secret={$secret}&redirect_uri={$redirect_uri}&code={$code}";
$url = $url.$urlPath.$paramstr;
return Curl::curl_post($url,[]);
}
/**
* 生成签名
*
1、 构造urlPath。urlPath是url 中的一部分,从协议开始截取,到“?”为止包含了协议、namespace、apiName和apiVersion如urlPath=http/1/system/currentTime/1688
2、 拼装参数。首先将所有参数按照key+value拼装得到一个字符串数组如[b1,a2]),然后对数组进行排序([a2,b1]最后将排序后的数组合并为字符串a2b1
3、 合并。把前两步的字符串拼接http/1/system/currentTime/1688a2b1
4、 执行hmac_sha1算法。 Signature=uppercase (hex (hmac_sha1 (concatString, secretKey))假设secretKey=test123那么得到的签名为2F1E96587451DE0E171978F4AAE1A90FF9B2F94B
a) concatString为合并后的字符串
b) secretKey为签名密钥与urlPath中的appKey1000000对应
c) hmac_sha1为通用的hmac_sha1算法各编程语言一般都对应类库
d) hex为转为十六进制
e) uppercase为转为大写字符
* authorwh
* @param string $urlPath eg: param2/1/com.alibaba.trade/alibaba.trade.fastCreateOrder/7571561
* @param array $params 请求参数
* @return string
*/
public function signature($urlPath,$params)
{
$accessToken = $this->config['accessToken'];
$params['access_token'] = $accessToken;
//$url = 'http://gw.open.1688.com/openapi';//1688开放平台使用gw.open.1688.com域名
//$appKey = $this->config['appkey'];
$appSecret = $this->config['appsecret'];
//dump('$urlPath: '.$urlPath);
//$apiInfo = 'param2/1/system/currentTime/' . $appKey;//此处请用具体api进行替换
//$urlPath = $urlPath . $appKey;//此处请用具体api进行替换
//配置参数请用apiInfo对应的api参数进行替换
$aliParams = array();
foreach ($params as $key => $val) {
$aliParams[] = $key . $val;
}
sort($aliParams);
$sign_str = join('', $aliParams);
$sign_str = $urlPath . $sign_str;
$code_sign = strtoupper(bin2hex(hash_hmac("sha1", $sign_str, $appSecret, true)));
return $code_sign;
}
/**
* desc发起请求自动完成签名并返回结果
* authorwh
* @param string $urlPath eg: param2/1/com.alibaba.trade/alibaba.trade.fastCreateOrder/23222222
* @param array $request_data 接口请求数据
*/
function request($urlPath, $request_data){
set_time_limit(0);
$request_params_str = '';
foreach ($request_data as $k=>$v){
if($request_params_str == ''){
$request_params_str .= $k.'='.$v;
}else{
$request_params_str .= '&'.$k.'='.$v;
}
}
$accessToken = $this->config['accessToken'];
//dump($request_params_str);
$_aop_signature = $this->signature($urlPath,$request_data);
//dump($_aop_signature);
//$request_data['_aop_signature'] = $_aop_signature;
$url = $this->base_url.$urlPath.'?'.$request_params_str.'&access_token='.$accessToken.'&_aop_signature='.$_aop_signature;
//dump($url);
$res = Curl::curl_post($url,[]);
return $res;
}
}

View File

@@ -0,0 +1,23 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/6/29} {12:23}
*/
namespace wanghua\general_utility_tools_php\alibaba\distributes;
class BaseStrict
{
public $base_url = 'https://gw.open.1688.com/openapi/';
//授权对象
protected $authObj = null;
public function __construct($authObj)
{
$this->authObj = $authObj;
//初始化并设置当前模块的基础url
$this->authObj->base_url = $this->base_url;
}
}

View File

@@ -0,0 +1,3 @@
## distributes 分销模块
### strict 分销中严选模块(商家自营)

View File

@@ -0,0 +1,144 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/6/29} {12:57}
*/
namespace wanghua\general_utility_tools_php\alibaba\distributes;
use app\common\model\TabConf;
use wanghua\general_utility_tools_php\alibaba\AlibabaAuth;
use wanghua\general_utility_tools_php\alibaba\distributes\strict\CustomerService;
use wanghua\general_utility_tools_php\alibaba\distributes\strict\StrictCategory;
use wanghua\general_utility_tools_php\alibaba\distributes\strict\StrictGoods;
use wanghua\general_utility_tools_php\alibaba\distributes\strict\StrictLogistics;
use wanghua\general_utility_tools_php\alibaba\distributes\strict\StrictOrder;
use think\Db;
use wanghua\general_utility_tools_php\alibaba\distributes\strict\StrictPay;
/**
* 测试案例
*
* Class Testexample
* @package wanghua\general_utility_tools_php\alibaba\distributes
*/
class Testexample
{
/**
* desc查询订单是否开通免密支付
*/
function queryOrderIsNoPassPay(){
$config = config('meebo_supply_config');
$authObj = new AlibabaAuth($config);
$obj = new StrictPay($authObj);
return $obj->queryOrderIsNoPassPay();
}
function getLogisticsTemplate(){
$config = config('meebo_supply_config');
$authObj = new AlibabaAuth($config);
$obj = new StrictLogistics($authObj);
return $obj->getLogisticsTemplate();
}
//物流模板详情
function getLogisticsTemplateDetail($template_id){
$config = config('meebo_supply_config');
$authObj = new AlibabaAuth($config);
$obj = new StrictLogistics($authObj);
return $obj->getLogisticsTemplateDetail($template_id);
}
function getGoodsList($where){
set_time_limit(0);
$config = config('meebo_supply_config');
$authObj = new AlibabaAuth($config);
$obj = new StrictGoods($authObj);
return $obj->getGoodsList($where);
}
function getGoodsListsPft($where){
$config = config('meebo_supply_config');
$authObj = new AlibabaAuth($config);
$obj = new StrictGoods($authObj);
return $obj->getGoodsListsPft($where);
}
function getGoodsDetail(array $itemIds){
$config = config('meebo_supply_config');
$authObj = new AlibabaAuth($config);
$obj = new StrictGoods($authObj);
return $obj->getGoodsDetail($itemIds);
}
function getCategoryByCID($category_id){
$config = config('meebo_supply_config');
$authObj = new AlibabaAuth($config);
$obj = new StrictCategory($authObj);
return $obj->getCategoryByCID($category_id);
}
function testGetRootCategory(){
$config = config('meebo_supply_config');
$authObj = new AlibabaAuth($config);
$obj = new StrictCategory($authObj);
return $obj->getRootCategory();
}
function testWebAuth($redirect_uri){
$config = config('meebo_supply_config');
$res = (new AlibabaAuth($config))->webAppAuth($redirect_uri);
return $res;
}
function getTokenByCode($code,$redirect_uri){
$config = config('meebo_supply_config');
$res = (new AlibabaAuth($config))->getTokenByCode($code,$redirect_uri);
return $res;
}
//测试创建订单
function createOrder(){
$order_info = Db::table(TabConf::$fa_order)
->where('orderid','mm70wgex1719586377768')
->find();
$address = Db::table(TabConf::$fa_useraddress)
->where('id',$order_info['useraddress_id'])
->where('users_id',$order_info['users_id'])
->find();
$goods = Db::table(TabConf::$fa_goods)
->where('id',$order_info['goods_id'])
->find();
$config = config('meebo_supply_config');
$authObj = new AlibabaAuth($config);
$order = new StrictOrder($authObj);
$order->setAddress($address);
$order->setGoods($goods,$order_info['buy_num']);
return $order->createOrder($order_info);
}
function previewOrder(){
$order_info = Db::table(TabConf::$fa_order)
->where('orderid','mm70wgex1719586377768')
->find();
$address = Db::table(TabConf::$fa_useraddress)
->where('id',$order_info['useraddress_id'])
->where('users_id',$order_info['users_id'])
->find();
$goods = Db::table(TabConf::$fa_goods)
->where('id',$order_info['goods_id'])
->find();
$config = config('meebo_supply_config');
$authObj = new AlibabaAuth($config);
$order = new StrictOrder($authObj);
$order->setAddress($address);
$order->setGoods($goods,$order_info['buy_num']);
return $order->previewOrder();
}
//获取唤起旺旺聊天的链接
function getCustomerServiceLink($toOpenUid){
$config = config('meebo_supply_config');
$authObj = new AlibabaAuth($config);
$order = new CustomerService($authObj);
return $order->getCustomerServiceLink($toOpenUid);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/7/9} {13:42}
*/
namespace wanghua\general_utility_tools_php\alibaba\distributes\notify;
use wanghua\general_utility_tools_php\Mmodel;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* @deprecated 弃用,通知属于业务模块,直接在业务控制器中处理,这里仅作参考
* 分销严选消息通知基类
*
* 初始化该基类,按需调用通知处理类
*
* Class BaseStrictNotify
* @package app\index\logic\alibaba\distributes\notify
*/
class BaseStrictNotify
{
private $object = null;
/**
* desc执行消息处理
* authorwh
* @param $callback
* @return array
*/
function doMessage($callback=null){
return Mmodel::catch(function () use ($callback){
Tools::log_to_write_txt(['执行消息处理']);
//根据消息类型,选择对应的处理类
$message_data = input('message');
if(empty($message_data)){
return Tools::set_fail('message为空');
}
$message_data = json_decode($message_data,true);
Tools::log_to_write_txt(['消息解码'=>$message_data]);
if(empty($message_data['type'])){
return Tools::set_fail('type为空');
}
//获取消息类型
$fn = strtolower($message_data['type']);//类型即为方法名
//消息类型前缀即为类名
$class = '\\app\\index\\logic\\alibaba\\distributes\\notify\\'.ucfirst(explode('_',$fn)[0]).'Notify';
$this->object = (new $class());//实例化类
return $this->object->{$fn}($callback);//调用方法
});
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/7/9} {13:25}
*/
namespace wanghua\general_utility_tools_php\alibaba\distributes\notify;
use app\index\logic\alibaba\distributes\Testexample;
use wanghua\general_utility_tools_php\Mmodel;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* @deprecated 弃用,通知属于业务模块,直接在业务控制器中处理,这里仅作参考
* 产品 消息通知
*
* Class ProductNotify
* @package app\index\logic\alibaba\distributes\notify
*/
class ProductNotify extends BaseStrictNotify
{
/**
* @deprecated 弃用,通知属于业务模块,直接在业务控制器中处理,这里仅作参考
* desc商品上架通知
* authorwh
*
* 输出message
{"message":"{\"data\":{\"productIds\":\"107680826\",\"msgSendTime\":\"2018-05-30 20:29:37\",\"memberId\":\"shyxsscl\",\"status\":\"RELATION_VIEW_PRODUCT_REPOST\"},\"gmtBorn\":1720514151217,\"msgId\":88922343751,\"type\":\"PRODUCT_RELATION_VIEW_PRODUCT_REPOST\",\"userInfo\":\"b2b-1881150273\"}","_aop_signature":"03EB6538BB8864BC141ADB5A4923005BB0204F85"}
*/
function product_relation_view_product_repost($callback=null)
{
return Mmodel::catch(function () use ($callback){
$message_data = input('message');
if(empty($message_data)){
return Tools::set_fail('message不能为空');
}
$message_data = json_decode($message_data,true);
if(empty($message_data['data'])){
return Tools::set_fail('data不能为空');
}
$productIds = $data['productIds'];//商品ID集合至少有一个用逗号分割
$productIdsArr = explode(',',$productIds);
});
}
}

View File

@@ -0,0 +1,3 @@
#这是异步通知案例
##业务逻辑自行编写

View File

@@ -0,0 +1,27 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/7/9} {0:43}
*/
namespace wanghua\general_utility_tools_php\alibaba\distributes\strict;
use wanghua\general_utility_tools_php\alibaba\distributes\BaseStrict;
class CustomerService extends BaseStrict
{
/**
* desc获取唤起旺旺聊天的链接
* authorwh
* @param $toOpenUid
* @return mixed
*/
function getCustomerServiceLink($toOpenUid){
$urlPath = "param2/1/com.alibaba.account/account.wangwangUrl.get/".$this->authObj->config['appkey'];
return $this->authObj->request($urlPath, ['toOpenUid'=>$toOpenUid]);
}
}

View File

@@ -0,0 +1,4 @@
## 分销严选模块
只能使用分销严选方案里的接口
DOC:
https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.product:alibaba.category.get-1&aopApiCategory=category_new

View File

@@ -0,0 +1,63 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/6/29} {12:12}
*/
namespace wanghua\general_utility_tools_php\alibaba\distributes\strict;
use wanghua\general_utility_tools_php\alibaba\distributes\BaseStrict;
/**
* 根据类目Id查询类目
*
* 传0获取所有一级类目循环调用可以获取类目树
*
* 注意类目id,必须大于等于0 如果为0则查询所有一级类目
*
* 类目查询。如果需要获取所有1688类目信息需要从根类目开始遍历获取整个类目树。
* 即先传0获取所有一级类目ID然后在通过获取到的一级类目ID遍历获取所二级类目最后通过遍历二级类目ID获取三级类目。
* 注意1688类目仅三级三级类目即发布商品所需的叶子类目。
*
* doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.product:alibaba.category.get-1&aopApiCategory=category_new
* Class StrictCategory
* @package app\index\logic\alibaba\distributes\strict
*/
class StrictCategory extends BaseStrict
{
public function __construct($authObj)
{
parent::__construct($authObj);
}
/**
* desc获取严选一级分类顶级分类
* authorwh
* @return mixed
*/
function getRootCategory(){
//$base_url = "https://gw.open.1688.com/openapi/";
$urlPath = "param2/1/com.alibaba.product/alibaba.category.get/".$this->authObj->config['appkey'];
$request_data = [];
$request_data['categoryID'] = '0';
return $this->authObj->request($urlPath,$request_data);
}
/**
* desc根据类目id获取类目信息
* authorwh
* @param $category_id
* @return mixed
*/
function getCategoryByCID($category_id){
$urlPath = "param2/1/com.alibaba.product/alibaba.category.get/".$this->authObj->config['appkey'];
$request_data = [];
$request_data['categoryID'] = $category_id;
return $this->authObj->request($urlPath,$request_data);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,39 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/6/30} {1:54}
*/
namespace wanghua\general_utility_tools_php\alibaba\distributes\strict;
use wanghua\general_utility_tools_php\alibaba\distributes\BaseStrict;
/**
* 物流
* Class StrictLogistics
* @package app\index\logic\alibaba\distributes\strict
*/
class StrictLogistics extends BaseStrict
{
/**
* 获取物流模板详情
*
* 根据物流模版ID获取卖家的物流模板。运费模板ID为0表示运费说明为1表示卖家承担运费
*
* doc:https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.logistics:alibaba.logistics.myFreightTemplate.list.get-1&aopApiCategory=Logistics_NEW
*/
function getLogisticsTemplateDetail($template_id){
$urlPath = "param2/1/com.alibaba.logistics/alibaba.logistics.myFreightTemplate.list.get/".$this->authObj->config['appkey'];
return $this->authObj->request($urlPath, ['templateId'=>$template_id]);
}
function getLogisticsTemplate(){
$urlPath = "param2/1/com.alibaba.logistics/alibaba.logistics.myFreightTemplate.list.get/".$this->authObj->config['appkey'];
return $this->authObj->request($urlPath, []);
}
}

View File

@@ -0,0 +1,250 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/6/28} {15:11}
*/
namespace wanghua\general_utility_tools_php\alibaba\distributes\strict;
use think\Exception;
use wanghua\general_utility_tools_php\http\Curl;
use wanghua\general_utility_tools_php\alibaba\distributes\BaseStrict;
/**
* 分销订单
* Class Order
* @package app\index\logic\alibaba\distributes
*/
class StrictOrder extends BaseStrict
{
//用户收货地址
private $address = [];
//商品信息
private $goods = [];
//发票信息
private $invoice = [];
//交易类型 由于不同的商品支持的交易方式不同没有一种交易方式是全局通用的所以当前下单可使用的交易方式必须通过下单预览接口的tradeModeNameList获取。
private $tradeType = '';
/**
* desc收货地址说明
1、使用保存的收货地址传参示例{"addressId":593861699}其中addressId是调用“买家获取保存的收货地址信息列表”接口获取
2、使用地址编码传参示例{"address":"桃浦镇 金达路888号贸易8楼","phone": "0517-88990077","mobile": "15251667788","fullName": "张三","postCode": "000000","districtCode": "310107"}其中districtCode需要调用“根据地址解析地区码”接口获取
3、直接使用文本地址示例{"address":"网商路699号","phone": "0517-88990077","mobile": "15251667788","fullName": "张三","postCode": "000000","areaText": "滨江区","townText": "","cityText": "杭州市","provinceText": "浙江省"},省市区要传文本;
4、优先级说明如果同时传入以上参数系统按从1至3的优先级获取地址满足1的条件下不会使用上示2中的参数满足2的条件下不会使用上示3中的参数
* authorwh
* @param $address
*/
function setAddress($address){
//{
//"address":"网商路699号",
//"phone":"0517-88990077",
//"mobile":"15251667788",
//"fullName":"张三",
//"postCode":"000000",
//"areaText":"滨江区",
//"townText":"",
//"cityText":"杭州市",
//"provinceText":"浙江省"
//}
//用户收货地址
$this->address = [
//'addressId'=>$address['id'],
'address'=>$address['address'],//街道地 网商路699号
'phone'=>$address['mobile'],//电话 0517-88990077
'mobile'=>$address['mobile'],//手机
'fullName'=>$address['name'],
'postCode'=>'000000',//邮编 000000
'areaText'=>$address['area'],//区文本
'townText'=>$address['town'],//镇文本
'cityText'=>$address['city'],//市文本
'provinceText'=>$address['provice'],//省份文本
//'districtCode'=>'000000',//地址编码 310107
];
}
function setGoods($goods,$buy_num){
$this->goods = [
'offerId'=>$goods['offerid'],
'specId'=>$goods['specid'],//商品规格id
'quantity'=>$buy_num,//$goods['quantity'],//商品数量(计算金额用)
];
}
/**
* desc创建分销订单
*
* 获取订单运费,请调用预览订单接口 alibaba.createOrder.preview
* authorwh
*/
function createOrder($order){
if(empty($this->authObj)){
throw new Exception('未初始化授权对象');
}
if(empty($this->address)){
throw new Exception('未设置收货地址');
}
if(empty($this->goods)){
throw new Exception('未设置商品信息');
}
//$base_url = "https://gw.open.1688.com/openapi/";
$urlPath = "param2/1/com.alibaba.trade/alibaba.trade.fastCreateOrder/".$this->authObj->config['appkey'];
if(empty($this->tradeType)){
throw new Exception('交易类型必须');
}
$request_data = ['tradeType'=>$this->tradeType];
//boutiquefenxiao(新分销严选)
$request_data['flow'] = 'boutiquefenxiao';
$request_data['isvBizTypeStr'] = 'fenxiaoMedia';
$request_data['message'] = $order['remark'];//买家留言
//地址
$request_data['addressParam'] = json_encode($this->address,JSON_UNESCAPED_UNICODE);
//商品信息
$request_data['cargoParamList'] = json_encode($this->goods,JSON_UNESCAPED_UNICODE);
//发票信息
$request_data['invoiceParam'] = json_encode($this->invoice,JSON_UNESCAPED_UNICODE);
$res = $this->authObj->request($urlPath, $request_data);
return $res;
}
/**
* 创建订单前预览数据接口
*
* 订单创建只允许购买同一个供应商的商品。
* 本接口返回创建订单相关的优惠等信息。
* 1、校验商品数据是否允许订购。 2、校验代销关系 3、校验库存、起批量、是否满足混批条件
*
* doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.createOrder.preview-1&aopApiCategory=trade_new
*/
function previewOrder(){
$urlPath = "param2/1/com.alibaba.trade/alibaba.createOrder.preview/".$this->authObj->config['appkey'];
$request_data = [];
//boutiquefenxiao(新分销严选)
$request_data['flow'] = 'boutiquefenxiao';
//$request_data['isvBizTypeStr'] = 'fenxiaoMedia';
//$request_data['message'] = $order['remark'];//买家留言
//地址
$request_data['addressParam'] = json_encode($this->address,JSON_UNESCAPED_UNICODE);
//商品信息
$request_data['cargoParamList'] = json_encode($this->goods,JSON_UNESCAPED_UNICODE);
//发票信息
$request_data['invoiceParam'] = json_encode($this->invoice,JSON_UNESCAPED_UNICODE);
$res = $this->authObj->request($urlPath, $request_data);
return $res;
}
/**
* 取消交易
*
* 买家或者卖家取消交易注意只有特定状态的交易才能取消1688可用于取消未付款的订单。
*
* doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.cancel-1&aopApiCategory=trade_new
*
* authorwh
* @param string $order_id 订单号
* @param string $cancelReason 原因描述buyerCancel:买家取消订单;sellerGoodsLack:卖家库存不足;other:其它
*
* 返回:{
"success": true
}
*/
function cancelOrder($order_id, $cancelReason, $remark=''){
$urlPath = "param2/1/com.alibaba.trade/alibaba.trade.cancel/".$this->authObj->config['appkey'];
$request_data = [];
$request_data['tradeID'] = $order_id;
$request_data['webSite'] = 1688;
//原因描述buyerCancel:买家取消订单;sellerGoodsLack:卖家库存不足;other:其它
$request_data['cancelReason'] = $cancelReason;
if($remark){
$request_data['remark'] = $remark;
}
$res = $this->authObj->request($urlPath, $request_data);
return $res;
}
/**
* 修改订单备忘
*
* 授权用户为卖家修改卖家备忘,授权用户为买家修改买家备忘 注意:该接口可重复调用,备注内容将覆盖前一次调用
*
* doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.order.memoAdd-1&aopApiCategory=trade_new
*
*/
function updateOrderRemark($order_id,$remark){
$urlPath = "param2/1/com.alibaba.trade/alibaba.order.memoAdd/".$this->authObj->config['appkey'];
$request_data = [];
$request_data['orderId'] = $order_id;
$request_data['memo'] = $remark;
//备忘图标目前仅支持数字。1位红色图标2为蓝色图标3为绿色图标4为黄色图标
$request_data['remarkIcon'] = 2;
$res = $this->authObj->request($urlPath, $request_data);
return $res;
}
/**
* 根据地址解析地区码
*
* 根据地址信息,解析地区码
*
* doc:https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.addresscode.parse-1&aopApiCategory=trade_new
*
*返回:{
"result": {
"address": "网商路699号",
"addressCode": "330108",
"isDefault": false,
"latest": false,
"postCode": "310051"
}
}
*/
function getAreaCode($address){
$urlPath = "param2/1/com.alibaba.trade/alibaba.trade.addresscode.parse/".$this->authObj->config['appkey'];
$request_data = [];
//地址信息 eg: 浙江省 杭州市 滨江区网商路699号
$request_data['addressInfo'] = $address;
$res = $this->authObj->request($urlPath, $request_data);
return $res;
}
/**
* 获取交易地址代码表详情
*
* 获取交易地址代码表该API会返回输入code的详情和该code的下一级地区code.
*
* doc: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade:alibaba.trade.addresscode.get-1&aopApiCategory=trade_new
*
* 返回:{
"result": {
"code": "330108",
"name": "滨江区",
"parentCode": "330100",
"post": "310051",
"children": ["330108001",
"330108002",
"330108003"]
}
}
*/
function getAreaCodeDetail($areaCode){
$urlPath = "param2/1/com.alibaba.trade/alibaba.trade.addresscode.get/".$this->authObj->config['appkey'];
$request_data = [];
//地址code码 eg: 330108
$request_data['areaCode'] = $areaCode;
$request_data['webSite'] = 1688;
$res = $this->authObj->request($urlPath, $request_data);
return $res;
}
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/6/30} {12:14}
*/
namespace wanghua\general_utility_tools_php\alibaba\distributes\strict;
use wanghua\general_utility_tools_php\alibaba\distributes\BaseStrict;
/**
* 支付相关
*
* Class StrictPay
* @package app\index\logic\alibaba\distributes\strict
*/
class StrictPay extends BaseStrict
{
/**
* desc 查询订单是否开通免密支付
* authorwh
*/
function queryOrderIsNoPassPay(){
$urlPath = "param2/1/com.alibaba.trade/alibaba.trade.pay.protocolPay.isopen/".$this->authObj->config['appkey'];
$request_data = [];
$res = $this->authObj->request($urlPath, $request_data);
return $res;
}
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* description
* authorwh
* email
* createTime{2021/08/24} {10:20}
*/
namespace wanghua\general_utility_tools_php\alipay;
use app\index\model\AppOrderPayReqRecordModel;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* 调起支付宝支付
*
* Class AlipayLogic
*
* 依赖:
* composer require wanghua/general-utility-tools-php dev-master
* @deprecated
*/
class AlipayLogic
{
/**
* desc调起支付
* authorwh
* @return false|mixed|\SimpleXMLElement|string|\提交表单HTML文本|null
* @throws \think\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
* @throws \think\exception\PDOException
*/
//function pay(string $notify_url,string $return_url=''){
///* *
// * 功能:支付宝手机网站支付接口(alipay.trade.wap.pay)接口调试入口页面
// * 版本2.0
// * 修改日期2016-11-01
// * 说明:
// * 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
// 请确保项目文件有可写权限,不然打印不了日志。
// */
//
//header("Content-type: text/html; charset=utf-8");
//
//
//require_once Tools::get_root_path().'library/AlipayTradeWapPay/wappay/service/AlipayTradeService.php';
//require_once Tools::get_root_path().'library/AlipayTradeWapPay/wappay/buildermodel/AlipayTradeWapPayContentBuilder.php';
////require dirname ( __FILE__ ).DIRECTORY_SEPARATOR.'./../config.php';
//$config = config('pay_config.alipay');
//if(empty($_POST['WIDout_trade_no']) || trim($_POST['WIDout_trade_no'])=="") return null;
////商户订单号,商户网站订单系统中唯一订单号,必填
//$out_trade_no = input('WIDout_trade_no');
//if(empty($out_trade_no)){
// return '参数错误';
//}
//$order = AppOrderPayReqRecordModel::getOrder($out_trade_no);
//if(empty($order)){
// return '订单错误';
//}
////超时时间
//$timeout_express="1m";
//
//$payRequestBuilder = new \AlipayTradeWapPayContentBuilder();
//$payRequestBuilder->setBody($order['goods_name']);
//$payRequestBuilder->setSubject($order['goods_name']);
//$payRequestBuilder->setOutTradeNo($out_trade_no);
//$payRequestBuilder->setTotalAmount($order['goods_price']);
//$payRequestBuilder->setTimeExpress($timeout_express);
//
//$payResponse = new \AlipayTradeService($config);
//$result = $payResponse->wapPay($payRequestBuilder,$return_url,$notify_url);
//return $result;
//}
}

View File

@@ -0,0 +1,195 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/6/10} {23:15}
*/
namespace wanghua\general_utility_tools_php\alipay\pay;
use Alipay\EasySDK\Kernel\Config;
use Alipay\EasySDK\Kernel\Factory;
use app\common\model\TabConf;
use app\common\tools\EmailTool;
use app\index\logic\AlipayBarcodePayLogic;
use app\index\logic\CheckstandLogic;
use app\index\model\OrderModel;
use app\index\model\PartnerAccountModel;
use think\Db;
use wanghua\general_utility_tools_php\phpmailer\Exception;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* 支付宝面对面扫码支付(用户出示付款码)
*
* 【扫码支付专用】
*
* 依赖:"alipaysdk/easysdk": "^2.2" || composer require alipaysdk/easysdk
*
* Class AlipayScanPay
* @package wanghua\general_utility_tools_php\alipay\transfer
*/
class AlipayScanPay
{
/**
* 支付参数配置
* @var array
*/
private array $config = [];
function __construct($config)
{
/** 设置支付参数 */
$this->config = $config;
}
/**
* desc面对面扫码支付-(扫用户的付款码)
*
* 注意:
* 免密支付是同步返回支付结果,输入密码支付是异步通知支付结果,
* 所以需要处理异步支付结果通知。
* @param array $order 订单信息
* @param string $auth_code 扫码枪扫描后的授权码
* @return array 返回支付结果(一维数组)
*/
function scanPay($order,$auth_code){
if(empty($auth_code)){
return Tools::set_res(500,'授权码错误');
}
try {
//1. 设置参数(全局只需设置一次)
Factory::setOptions($this->getOptions());
//2. 发起API调用以支付能力下的统一收单交易创建接口为例
$result = Factory::payment()->faceToFace()
->optional('scene','bar_code')
->pay($order['title'], $order['orderid'], $order['real_amount'], $auth_code);
if($result->code == 10003){
return Tools::set_res(230,'交易进行中(提示用户输入密码)'.$result->msg);
}
//必须解析json
$resp = json_decode($result->httpBody, true);
//支付宝面对面扫码支付后续流程
return Tools::set_ok('ok',$resp['alipay_trade_pay_response']);//返回支付结果(一维数组)
} catch (\Exception $e) {
$err_data = [
'error'=>'支付宝付款码支付错误'.$e->getMessage(),
'error_info'=>$e->getTraceAsString()
];
Tools::log_to_write_txt($err_data);
return Tools::set_fail('错误.'.$e->getMessage());
}
}
/**
* desc获取支付配置
* authorwh
* @return Config
* @throws Exception
*/
private function getOptions()
{
if(empty($this->config)){
throw new Exception('支付宝配置错误');
}
if(empty($this->config['notifyUrl'])){
throw new Exception('支付宝配置错误notifyUrl通知地址字段必须配置免密支付是同步返回支付结果输入密码支付是异步通知支付结果');
}
$config = $this->config;
$options = new Config();
$options->protocol = 'https';
$options->gatewayHost = 'openapi.alipay.com';
$options->signType = $config['sign_type'];
$options->appId = $config['app_id'];//'<-- 请填写您的AppId例如2019022663440152 -->';
// 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
$options->merchantPrivateKey = $config['alipay_app_private_key'];//'<-- 请填写您的应用私钥例如MIIEvQIBADANB ... ... -->';
$options->alipayCertPath = Tools::get_root_path().$config['cert_path_alipayCertPublicKey_RSA2'];//'<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->';
$options->alipayRootCertPath = Tools::get_root_path().$config['cert_path_alipayRootCert'];//'<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt" -->';
$options->merchantCertPath = Tools::get_root_path().$config['appCertPublicKey'];//'<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->';
//注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
// $options->alipayPublicKey = '<-- 请填写您的支付宝公钥例如MIIBIjANBg... -->';
//可设置异步通知接收服务地址(可选)
$options->notifyUrl = $this->config['notifyUrl'];//request()->domain().'/index/alipay/notify';//"<-- 请填写您的支付类接口异步通知接收服务地址例如https://www.test.com/callback -->";
return $options;
}
/**
* desc支付宝扫码异步通知
*
* 【通知结果是一维数组】
*
* {"gmt_create":"2024-06-10 11:53:37","charset":"UTF-8","seller_email":"15023017325",
* "subject":"购物消费",
* "sign":"oCR3X0ViRrydigLMeOCTrMBNx7O8bSZnEflT0qZYo0gXk2jTI2OVB9mG2UPCkGuSND3wq/pMRFN
* ljuz+2r3KMha9a6ArD/ey8w1am3yCKI+FMqUMQSv9TJqfpWdAsc8B45egtr+txBBOA1NMfN41hPnTxM3QzQ82cm
* 7VYw6pnYpxJVj/3AsqYxY+wcyMq5+v25eC4TgBo+YJ9pfb3rqaAV91cJBoHRq1Cwh0XEnOzm9zpv7b5cu3r+V0oeW
* wTCCi8S1TXW2dfE4H8o63xgASYfJO2fgGIu19YICQdUONkDhu9Tfbf3fFf9SHw3RIw8zrQRqkwAXC32NwfUv4Ru5NqQ==",
* "buyer_id":"2088612757425274","invoice_amount":"3258.60","notify_id":"2024061001222115350025271439748729",
* "fund_bill_list":"[{\"amount\":\"3258.60\",\"fundChannel\":\"ALIPAYACCOUNT\"}]",
* "notify_type":"trade_status_sync","trade_status":"TRADE_SUCCESS","receipt_amount":"3258.60",
* "app_id":"2021004136614333","buyer_pay_amount":"3258.60","sign_type":"RSA2",
* "seller_id":"2088642975857443","gmt_payment":"2024-06-10 11:53:50",
* "notify_time":"2024-06-10 12:07:40","version":"1.0","out_trade_no":"cs25obpv1717991615660",
* "total_amount":"3258.60","trade_no":"2024061022001425271426453509","auth_app_id":"2021004136614333",
* "buyer_logon_id":"146***@qq.com","point_amount":"0.00"}
*
* authorwh
* param function $fn 传入订单处理逻辑函数 要求返回success、fail
* @return string
*/
function scanPayNotify($fn){
Tools::log_to_write_txt([
'支付宝异步通知',
'input'=>input()
]);
$out_trade_no = input('out_trade_no');
if(empty($out_trade_no)){
Tools::log_to_write_txt(['error'=>'out_trade_no为空,文件:'.__FILE__.',line:'.__LINE__]);
return '';
}
//$order = Db::table(TabConf::$fa_order)
// ->where('orderid',$out_trade_no)
// ->find();
//if(empty($order)){
// Tools::log_to_write_txt(['error'=>'订单不存在,文件:'.__FILE__.',line:'.__LINE__]);
// return '';
//}
//该字段用于判断成功、失败
$trade_status = input('trade_status');
if(empty($trade_status)){
Tools::log_to_write_txt(['error'=>'trade_status为空,文件:'.__FILE__.',line:'.__LINE__]);
return '';
}
if(empty($this->config['seller_id'])){
Tools::log_to_write_txt(['error'=>'卖家seller_id为空,文件:'.__FILE__.',line:'.__LINE__]);
return '';
}
//判断来源
$seller_id = input('seller_id');
if($seller_id != $this->config['seller_id']){
Tools::log_to_write_txt(['error'=>'支付通知来源错误','seller_id'=>$seller_id,'config'=>$this->config['seller_id']]);
return 'fail';
}
//支付宝面对面扫码支付后续流程
$result = input();
//异步通知
//$res = CheckstandLogic::alipayScanSuccessFlow($result,$order);
//if($res['code'] == 200 || $res['code'] == 230){//成功或进行中
// return 'success';
//}
return $fn($result);//要求返回success、fail
}
}

View File

@@ -0,0 +1,143 @@
<?php
/*
* description
* authorwh
* email
* createTime{2021/09/24} {19:43}
*/
namespace wanghua\general_utility_tools_php\alipay\transfer;
use Alipay\EasySDK\Kernel\Config;
use Alipay\EasySDK\Kernel\Factory;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* 支付宝转账(独立且区别于支付,连参数都不能用支付参数)
*
* 注:换句话说,转账是转账的参数,支付是支付的参数,不能混为一谈(以此备注长记性)。
*
* 环境要求PHP7.0+ThinkPHP5.0+
*
* 依赖:
* composer require alipaysdk/easysdk:^2.0
*
* composer require wanghua/general-utility-tools-php dev-master
*
* 重要说明:
*
* 支付宝开放平台可能有多个应用,可能存在一个默认应用(应用名称:应用2.0签约2020102692466480),转账配置就用此应用的参数,而不是其它应用的配置!
*
*/
class AlipayTransfer
{
/**
* desc转账到支付宝证书模式
* authorwh
* @param array $alipayConfig
* @param $transferConfig TransferConfig.php
* @return mixed
*
* 使用案例:
*
function transfer(array $trans_data){
$alipayConfig = \config('pay_config.alipay_transfer');//转账配置
$trans_config = new TransferConfig();
$trans_config->cert_path_alipayCertPublicKey_RSA2 = Tools::get_root_path().$alipayConfig['cert_path_alipayCertPublicKey_RSA2'];//'<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->';
$trans_config->cert_path_alipayRootCert = Tools::get_root_path().$alipayConfig['cert_path_alipayRootCert'];//'<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt" -->';
$trans_config->appCertPublicKey = Tools::get_root_path().$alipayConfig['appCertPublicKey'];//'<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->'
$order_prefix = 'trs';
$order_id = Tools::to_create_order_no($order_prefix);
$trans_config->order_id = $order_id;//订单号 必须
$trans_config->timestamp = Tools::get_now_date();//请求时间 eg2020-01-08 10:12:50
$trans_config->trans_amount = $trans_data['trans_amount'];//转账金额 必须 且为字符串 最低0.1元 取值范围[0.1,100000000]
$trans_config->order_title = $trans_data['order_title'];//订单标题 可选
$trans_config->phone = $trans_data['phone'];//支付宝登录手机号 必须
$trans_config->name = $trans_data['name'];//支付宝真实姓名 必须
$trans_config->remark = $trans_data['remark'];//支付备注 可选
$trans = new AlipayTransfer();
Tools::log_to_write_txt(['转账到支付宝,入参:'=>input(), 'trans_config'=>$trans_config]);
$pay_res = $trans->alitransfer($alipayConfig, $trans_config);
Tools::log_to_write_txt(['转账到支付宝,出参:'=>$pay_res]);
//直接返回转账结果自主验证成功失败的status。code=10000表示操作成功status=SUCCESS表示转账成功
//由于是同步响应,所以不需要对结果验签
return $pay_res;
}
*
*/
function alitransfer(array $alipayConfig, $transferConfig){
//$transferConfig = new TransferConfig();
Factory::setOptions($this->getAlipayOptions($alipayConfig, $transferConfig));
$method = 'alipay.fund.trans.uni.transfer';
//公共参数
$textParams = [
'app_id'=>$alipayConfig['app_id'],
'charset'=>'utf-8',
'sign_type'=>'RSA2',
'timestamp'=>$transferConfig->timestamp,
'version'=>'1.0',
];
//请求参数
//订单号前缀
$biz_content = [
'out_biz_no'=>$transferConfig->order_id,//商家支付订单号
'trans_amount'=>$transferConfig->trans_amount,
'product_code'=>'TRANS_ACCOUNT_NO_PWD',
'biz_scene'=>'DIRECT_TRANSFER',
'order_title'=>$transferConfig->order_title,
'payee_info'=>[
'identity'=>$transferConfig->phone,//手机号
'identity_type'=>'ALIPAY_LOGON_ID',
'name'=>$transferConfig->name,//参与方真实姓名如果非空将校验收款支付宝账号姓名一致性。当identity_type=ALIPAY_LOGON_ID时本字段必填。
],
'remark'=>$transferConfig->remark,
];
$pay_res = Factory::util()->generic()->execute($method,$textParams,$biz_content);
return $pay_res;
}
/**
* desc证书模式参数非证书模式参数需单独获取
* authorwh
* @param array $alipayConfig 支付宝转账配置(非支付配置)
* @return Config
*/
protected function getAlipayOptions(array $alipayConfig, $transferConfig)
{
$options = new Config();
$options->protocol = 'https';
$options->gatewayHost = 'openapi.alipay.com';
$options->signType = 'RSA2';
//'<-- 请填写您的AppId例如2019022663440152 -->'
$options->appId = $alipayConfig['app_id'];//'2021002103639985';
// 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
$options->merchantPrivateKey = $alipayConfig['alipay_app_private_key'];//'<-- 请填写您的应用私钥例如MIIEvQIBADANB ... ... -->';
$options->alipayCertPath = $transferConfig->cert_path_alipayCertPublicKey_RSA2;//'<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->';
$options->alipayRootCertPath = $transferConfig->cert_path_alipayRootCert;//'<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt" -->';
$options->merchantCertPath = $transferConfig->appCertPublicKey;//'<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->';
//注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
// $options->alipayPublicKey = $alipayConfig['alipay_public_key'];//'<-- 请填写您的支付宝公钥例如MIIBIjANBg... -->';
//可设置异步通知接收服务地址(可选)
$options->notifyUrl = $transferConfig->notifyUrl;//"<-- 请填写您的支付类接口异步通知接收服务地址例如https://www.test.com/callback -->";
//可设置AES密钥调用AES加解密相关接口时需要可选
//$options->encryptKey = "<-- 请填写您的AES密钥例如aa4BtZ4tspm2wnXLb1ThQA== -->";
return $options;
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* description
* authorwh
* email
* createTime{2021/09/24} {20:07}
*/
namespace wanghua\general_utility_tools_php\alipay\transfer;
/**
* 调用转账之前请配置此参数
*
* 配置示例:
*
$alipayConfig = \config('pay_config.alipay_transfer');//转账配置
$trans_config = new TransferConfig();
$trans_config->cert_path_alipayCertPublicKey_RSA2 = Tools::get_root_path().$alipayConfig['cert_path_alipayCertPublicKey_RSA2'];//'<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->';
$trans_config->cert_path_alipayRootCert = Tools::get_root_path().$alipayConfig['cert_path_alipayRootCert'];//'<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt" -->';
$trans_config->appCertPublicKey = Tools::get_root_path().$alipayConfig['appCertPublicKey'];//'<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->'
$order_prefix = 'trs';
$order_id = Tools::to_create_order_no($order_prefix);
$trans_config->order_id = $order_id;//订单号 必须
$trans_config->timestamp = Tools::get_now_date();//请求时间 eg2020-01-08 10:12:50
$trans_config->trans_amount = '0.21';//转账金额 必须 且为字符串 最低0.1元 取值范围[0.1,100000000]
$trans_config->order_title = '代理提现';//订单标题 可选
$trans_config->phone = '18290416033';//支付宝登录手机号 必须
$trans_config->name = '王华';//支付宝真实姓名 必须
$trans_config->remark = '代理提现到支付宝';//支付备注 可选
$trans = new AlipayTransfer();
$pay_res = $trans->alitransfer($alipayConfig, $trans_config);
dump($pay_res);
*
*/
class TransferConfig
{
//'<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->';
public $cert_path_alipayCertPublicKey_RSA2 = '';//必须
//'<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt" -->';
public $cert_path_alipayRootCert = '';//必须
//'<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->'
public $appCertPublicKey = '';//必须
public $notifyUrl = '';//接收异步通知(可选)
//业务参数
public $order_id = '';//订单号 必须
public $timestamp = '';//请求时间 必须 eg2020-01-08 10:12:50
public $trans_amount = '';//转账金额 必须 且为字符串 最低0.1元 取值范围[0.1,100000000]
public $order_title = '';//订单标题 可选
public $phone = '';//支付宝登录手机号 必须
public $name = '';//支付宝真实姓名 必须
public $remark = '';//支付备注 可选
}

View File

@@ -0,0 +1,425 @@
<?php
/*
* description
* authorwh
* email
* createTime{2022/02/24} {11:21}
*/
namespace wanghua\general_utility_tools_php\api;
use wanghua\general_utility_tools_php\mysql\lib\Table;
use wanghua\general_utility_tools_php\tool\Tools;
class Api extends BaseLibApi
{
private $apidir = '';
private $namespace = 'app\api\controller';
private $table_obj = null;
//一维数组
private $functions_arr = [];//要创建的初始方法
public function __construct()
{
//默认api目录
$this->apidir = Tools::get_root_path().'application/api/controller/';
if(!$this->table_obj)
$this->table_obj = new Table();
}
/**
* descapi目录
* authorwh
* @param string $dir api创建目录格式必须按照例子填写 eg: application/api/controller/
*/
function setapidir(string $dir){
$this->apidir = $dir;
$nsp = str_replace('application','app',substr($dir,0,strlen($dir)-2));
$this->namespace = str_replace('/','\\',$nsp);
}
/**
* desc替换表前缀
* authorwh
* @param $tablename
* @return false|string
*/
function replaceTablePrefix($tablename){
$prefix = config('database.prefix');
if($prefix){
return substr($tablename,strlen($prefix));
}
$tmp_prefix = 't_';
if(false !== strpos($tablename,$tmp_prefix)){
return substr($tablename,strlen($tmp_prefix));
}
return $tablename;
}
/**
* desc设置方法名称
*
* authorwh
* @param array $functions_arr
*/
function setApiFunctionsName(array $functions_arr){
$this->functions_arr = array_merge($functions_arr,$this->functions_arr);
}
/**
* desc动态创建方法
*
* authorwh
*/
private function syncCreateFunction(array $func_name_arr){
$code = '';
foreach ($func_name_arr as $func_name=>$func_remark){ //接口方法公共代码模块
$commonFunctionCodeModule = $this->commonFunctionCodeModule($func_remark);
$code .= <<<EOF
/**
* desc{$func_remark}
*
* author
*/
function {$func_name}(){
{$commonFunctionCodeModule}
}
EOF;
}
return $code;
}
/**
* desc创建查询接口的代码体
*
* 本接口只针对简单查询逻辑
*
* and
* like%%like%,
* >, <, >=, <=, !=
* between
* in
*
* authorwh
* @param array $select_in_params eg:
* [
* 'gameid'=>['and'],
* 'nickname'=>['like%'],
* 'reg_time'=>['between'],//时间区间查询
* 'audit_time'=>['2015-01-3','eq'],//时间区间查询
* ]
*
*/
function setQueryCodeBody(array $select_in_params){
$code = '';
return $code;
}
/**
* desc写入查询接口的代码体
*
* authorwh
* @param array $insert_params
* @return string
*/
function setInsertCodeBody(array $insert_params){
$code = '';
return $code;
}
/**
* desc快速构建api代码
*
* 本方法不验证接口名称是否重复
*
* authorwh
* @param string $tablename 数据表名称
* @param array $func_name_arr 方法名称数组 eg:['getData'=>'获取数据','function_aaa'=>'function_注释aaa']
*/
function buildApi(string $tablename, array $func_name_arr){
//deal table name
$tablename = $this->replaceTablePrefix($tablename);
//基础 start
//create base controller
$this->buildBaseApiPublicController();//开放控制器
$this->buildBaseApiAuthController();//权限控制器
$this->errController();//错误控制器
//基础 end
//接口类名
$classname = ucfirst(Tools::convertUnderLine($tablename));
//接口注释
$comment = $this->table_obj->getTableComment($tablename);
//批量构建方法代码
$build_functions_code = $this->syncCreateFunction($func_name_arr);
//PHP文件名不含物理路径
$file_name = $classname.$this->ext();
//PHP文件路径和文件名
$php_file = $this->apidir.$file_name;
if(file_exists($php_file)){
//追加代码
//读取并删除最后的“}”
$php_code_str = file_get_contents($php_file);
$sub_php_code_str = mb_substr($php_code_str,0,mb_strlen($php_code_str)-2);
//追加新的代码
$sub_php_code_str .= $build_functions_code ."\n }";
//清空代码
file_put_contents($php_file,'');
//追加
$this->createFileExists($php_file,$sub_php_code_str);
}else{
//创建代码文件
$use_tpl = $this->useTpl();
$php = <<<EOF
<?php
namespace {$this->namespace};
{$use_tpl}
/**
* {$comment}
* Class {$classname}
* @package {$this->namespace}
*/
class {$classname} extends BaseApiPublicController
{
{$build_functions_code}
}
EOF;
$this->createDir($this->apidir);
$this->createFile($php_file,$php);
}
}
/**
* desc公共方法模块
*
* authorwh
* @param $comment
* @return string
*/
private function commonFunctionCodeModule($comment){
$php = <<<EOF
try {
Tools::log_to_write_txt(['title'=>'{$comment},__IN','INPUT'=>input()]);
//region 校验区 start
//endregion 校验区 end
//region 业务逻辑区 start
//endregion 业务逻辑区 end
//响应结果处理
Tools::log_to_write_txt(['title'=>'{$comment},__OUT','OUTPUT'=>[]]);
return json(Tools::set_res(200,'ok'));
}catch (\Exception \$e){
Tools::log_to_write_txt([
'error'=>'{$comment},API异常。'.\$e->getMessage(),
'入参'=>input(),
'error_info'=>\$e->getTraceAsString()
]);
return json(Tools::set_res(500,'系统繁忙'));
}
EOF;
return $php;
}
/**
* descapi基础开放控制器
*
* 如果存在则不创建
*
* authorwh
*/
private function buildBaseApiPublicController(){
$file_name = 'BaseApiPublicController';
$php = <<<EOF
<?php
namespace {$this->namespace};
use think\Controller;
/**
* descapi基础开放架构
*
* 所有开放接口继承此父类
*
* author
*/
class {$file_name} extends Controller
{
}
EOF;
$php_file = $this->apidir.$file_name.$this->ext();
$this->createDir($this->apidir);
$this->createFile($php_file,$php);
}
/**
* descapi基础权限控制器
*
* 如果存在则不创建
*
* authorwh
*/
private function buildBaseApiAuthController(){
$file_name = 'BaseApiAuthController';
$php = <<<EOF
<?php
namespace {$this->namespace};
use think\Controller;
/**
* descapi基础权限架构
*
* 所有权限接口继承此父类
*
* author
*/
class {$file_name} extends Controller
{
}
EOF;
$php_file = $this->apidir.$file_name.$this->ext();
$this->createDir($this->apidir);
$this->createFile($php_file,$php);
}
/**
* descapi底层基础控制器架构
*
* [按需创建][可单独调用]
*
* 如果存在则不创建
*
* authorwh
*/
function buildBaseApiController(){
$file_name = 'BaseApiController';
$php = <<<EOF
<?php
namespace {$this->namespace};
use think\Controller;
/**
* descapi底层基础控制器架构
*
* author
*/
class {$file_name} extends Controller
{
}
EOF;
$php_file = $this->apidir.$file_name.$this->ext();
$this->createDir($this->apidir);
$this->createFile($php_file,$php);
}
/**
* descuse模块
*
* authorwh
* @return string
*/
private function useTpl(){
$php = <<<EOF
use wanghua\general_utility_tools_php\\tool\Tools;
EOF;
return $php;
}
/**
* desc错误控制器
*
* authorwh
*/
function errController(){
$file_name = 'Err';
$php = <<<EOF
<?php
namespace {$this->namespace};
use wanghua\general_utility_tools_php\\framework\\base\PublicController;
use wanghua\general_utility_tools_php\\tool\Tools;
/**
*
* 错误(异常)中转架构
*
* Class Err
* @package app\index\controller
*/
class {$file_name} extends BaseApiPublicController
{
/**
* desc校验失败中转
*
* 场景:内网端口验证未通过时跳转;
*
* authorwh
* @return \\think\\response\\Json
*/
function checkfailed(){
return json(Tools::set_res(500,'Permission denied',input()));
}
}
EOF;
$php_file = $this->apidir.$file_name.$this->ext();
$this->createDir($this->apidir);
$this->createFile($php_file,$php);
}
}

View File

@@ -0,0 +1,264 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/5/15} {21:40}
*/
namespace wanghua\general_utility_tools_php\api;
use wanghua\general_utility_tools_php\phpmailer\Exception;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* desc接口文档构建器
*
* 使用步骤:
* (new ApiDocument())->buildDoc();
* Class ApiDocument
*/
class ApiDocument
{
private $api_cache_arr = [];//缓存所有接口
public $api_domain = 'http://127.0.0.1:8080/';//接口域名/ip
/**
* @var string 接口控制器命名空间
*/
public $namespace = 'app\\api\\controller';
/**
* @var string 接口控制器基类精确到类名app\\common\\controller\\Api
* 注意:如果同级目录存在多个基类,则设置直接基类,如果同级目录没有基类,则设置底层基类
*/
public $extends_base_class = '';
//控制器目录物理路径
public $controllerDirectory = '';
/**
* @var string 接口文档保存路径
*/
public $api_docs_save_dir = 'public/api_docs/';
//设置过滤类
private $filterClassArr = [];
//设置过滤方法
private $filterFunctionArr = [];
public function __construct($api_domain='',$controllerDirectory='')
{
//默认如果是tp6那application就要改为app了自行传参吧
$this->controllerDirectory = Tools::get_root_path().'application/api/controller';
if($api_domain){
$this->api_domain = $api_domain;
}
if($controllerDirectory){
$this->controllerDirectory = $controllerDirectory;
}
}
/**
* desc设置过滤类类名
* 过滤场景:
* 1、基类
* 2、测试类
* 3、定时任务类
* 4、其它非必要类
* authorwh
* @param array $filterClassArr
*/
function setFilterClass(array $filterClassArr=[]){
$this->filterClassArr = $filterClassArr;
}
function setFilterFunction(array $filterArr=[]){
$this->filterFunctionArr = $filterArr;
}
/**
* desc构建接口文档支持同步到在线文档
* authorwh
*/
function buildDoc(){
if(empty($this->extends_base_class)){
throw new Exception('请设置接口控制器基类精确到类名app\\common\\controller\\Api');
}
$out_path = Tools::get_root_path().$this->api_docs_save_dir;
if(!file_exists($out_path)){
mkdir($out_path,0777,true);
}
$outputFile = $out_path.'api_list.md';
$controllerClasses = [];
// 搜索控制器目录下的所有PHP文件
foreach (glob($this->controllerDirectory . '/*.php') as $filename) {
// 获取文件中的类名
$class = basename($filename, '.php');
if(in_array($class,$this->filterClassArr)){
continue;
}
// 构建完整的命名空间类名
$fullClassName = $this->namespace . '\\' . $class;
foreach (explode(',',$this->extends_base_class) as $base_class){
// 检查类是否有效并且是think\Controller的子类
if (class_exists($fullClassName) && is_subclass_of($fullClassName, $base_class)) {
$controllerClasses[] = $fullClassName;
}
}
}
// 创建Markdown文件
$file = fopen($outputFile, 'w') or die('无法创建文件');
$head_text = <<<EOF
# API 文档
## 接口列表
###### ctrl+f 搜索)(如果更改了路由,请根据路由规则定位)
##### 请求域名:{$this->api_domain}
##### 请求方式POST默认
EOF;
// 写入Markdown文件头部
fwrite($file, $head_text);
foreach ($controllerClasses as $controllerClass) {
$reflector = new \ReflectionClass($controllerClass);
// 遍历控制器中的公共方法
$methods = $reflector->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
//过滤方法
if(in_array($method->name, $this->filterFunctionArr)){
continue;
}
$exp_class = explode('\\',$controllerClass);
//过滤类
if(in_array($exp_class[count($exp_class)-1],$this->filterClassArr)){
continue;
}
$comments = $method->getDocComment();
if ($comments) {
$this->processMethodComment($comments, $controllerClass, $method->name, $outputFile);
}
}
}
fclose($file);
//缓存所有接口
cache('api_doc_cache_arr',$this->api_cache_arr);
}
/**
* desc解析方法注释并写入Markdown文件
* authorwh
* @param $comments
* @param $className
* @param $methodName
* @param $savepath
*/
private function processMethodComment($comments, $className, $methodName, $savepath) {
if($methodName == '__construct'){
return '';
}
$api_url = "/api/{$className}/{$methodName}";
$js_api_func_name = "api_{$className}_{$methodName}";
$str = <<<EOF
***
```
EOF;
$comments_str = mb_substr($comments,0,-2);
$class_arr = explode('\\',$className);
$className = $class_arr[count($class_arr)-1];
$className = $this->camelCaseToUnderscore($className);
$api_name = "api/{$className}/{$methodName}";
$doc_txt = <<<EOF
* $api_name
*/
```
EOF;
$doc_txt= $str.$comments_str.$doc_txt." \r\n";
$this->api_cache_arr[] = ['api_name'=>$api_name,'doc_txt'=>$doc_txt];
file_put_contents($savepath,$doc_txt, FILE_APPEND);
}
/**
* desc驼峰转下划线
* authorwh
* @param $string
* @return string
*/
private function camelCaseToUnderscore($string) {
$str = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $string));
return strpos($str,'_')===0?substr($str,1):$str;
}
function buildApiDocHtml(){
$api_doc_cache_arr = cache('api_doc_cache_arr');
$htm_str = "";
$script_str = "";
foreach ($api_doc_cache_arr as $item){
$api_name = $item['api_name'];
$doc_txt = $item['doc_txt'];
$function_name = str_replace('/','_',$api_name);
$htm_str .= <<<EOF
<div id="{$function_name}">
<div class="markdown_content">{$doc_txt}</div>
<div>
按需填写其它接口参数:
<textarea name="" id="{$function_name}_textarea" cols="100" rows="3">/{$api_name}</textarea>
<a href='JavaScript:;' onclick="DocObject.{$function_name}()">测试</a>
</div>
<div class="{$function_name}_response_result"></div>
</div>
EOF;
$script_str.=<<<EOF
{$function_name}(){
let url = $('#{$function_name}_textarea').val();
$.post(url,{},function(res) {
$('.{$function_name}_response_result').html(JSON.stringify(res, null, "\\t"));
$('.{$function_name}_response_result').attr('style','color:green');
},'json');
},
EOF;
}
$html = <<<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>接口文档</title>
</head>
<body>
<div>
{$htm_str}
</div>
</body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/static/common/js/marked.min.js"></script>
<script>
$(function() {
//加载markdown
DocObject.markdown_content();
});
let DocObject = {
markdown_content(){
$('.markdown_content').each(function(k,ele) {
$(ele).html(marked.parse($(ele).html()));
});
},
$script_str
}
</script>
</html>
EOF;
return $html;
}
}

View File

@@ -0,0 +1,230 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/11/11} {16:14}
*/
namespace wanghua\general_utility_tools_php\api;
use wanghua\general_utility_tools_php\exception\api\ApiException;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* @deprecated 废弃
* descapi交互
*
* authorwh
* Class BaseApi
* @package app\apidata
*/
class BaseApi
{
protected $post_url = '';
private $prefix = 'api_';
protected $log_file_name = 'log';
protected $__sit__ = '';
function __construct(string $url)
{
$this->post_url = $url;
if(empty($this->post_url)) {
Tools::log_to_write_txt(['error'=>'API OPERATE: '.ApiException::EMPTY_URL_ERROR], $this->getLogFileName());
throw new \Exception(ApiException::EMPTY_URL_ERROR);
}
}
/**
* desc设置api请求日志文件名称
*
* 日志保存在runtime中
*
* authorwh
* @param string $log_file_name
*/
function setLogFileName(string $log_file_name=''){
if($log_file_name)$this->log_file_name = $log_file_name;
}
/**
* desc 获取记录日志的文件名
* authorwh
* @return string
*/
function getLogFileName(){
return $this->prefix.$this->log_file_name;
}
/**
* desc设置调用位置
* authorwh
* @param $__file__
* @param $__line__
*/
function setFileDir($__file__, $__line__){
$this->__sit__ = $__file__.' > '.$__line__;
}
/**
* desc错误位置
* authorwh
* @return string
*/
function getErrorSit(){
return $this->__sit__;
}
/**
* desc
*
* authorwh
* @param array $params
*/
private function mergeAuthParams(array &$params){
$params['nonce'] = Tools::rand_str(10);
$params['timestamp'] = time();
$token = config('service_framework_config.sign_token');
$params['sign'] = Tools::signature($params, $token);
}
/**
* desc执行post请求, 带权限参数
*
* 同框架、内部系统推荐
*
* 返回结果由code 错误码和msg 错误信息组成
*
* authorwh
* @param $param 以表单格式提交数据
* @return array|bool|int|mixed|string
* @throws ApiException
* @throws \think\Exception
*/
function apiPost($param){
$this->mergeAuthParams($param);
Tools::log_to_write_txt(['API OPERATE, IN',$this->post_url,$param], $this->getLogFileName());
$res = Tools::curl_post($this->post_url, $param);
Tools::log_to_write_txt(['API OPERATE, OUT',$this->post_url, $res], $this->getLogFileName());
if(empty($res['data'])) return $res;
$data = json_decode($res['data'], true);
if(empty($data['code'])) {
Tools::log_to_write_txt([
'error: api操作,错误',
'post_url'=>$this->post_url,
'error_info'=>ApiException::API_RESPONSE_FORMAT_ERROR,
'error_sit'=>$this->getErrorSit(),
'result'=>$res,
], $this->getLogFileName());
throw new \Exception(ApiException::API_RESPONSE_FORMAT_ERROR);
}
return $data;
}
/**
*
* desc执行post请求
*
* 返回结果由code 错误码和msg 错误信息组成
*
* authorwh
* @param $param 以json格式提交数据
* @return array|bool|int|mixed|string
* @throws ApiException
* @throws \think\Exception
*/
function do($param){
Tools::log_to_write_txt(['API OPERATE, IN',$this->post_url,$param], $this->getLogFileName());
$res = Tools::curl_post($this->post_url, json_encode($param));
Tools::log_to_write_txt(['API OPERATE, OUT',$this->post_url, $res], $this->getLogFileName());
if(empty($res['data'])) return $res;
$data = json_decode($res['data'], true);
if(empty($data['code'])) {
Tools::log_to_write_txt([
'error: api操作,错误',
'post_url'=>$this->post_url,
'error_info'=>ApiException::API_RESPONSE_FORMAT_ERROR,
'error_sit'=>$this->getErrorSit(),
'result'=>$res,
], $this->getLogFileName());
throw new \Exception(ApiException::API_RESPONSE_FORMAT_ERROR.$this->post_url);
}
return $data;
}
/**
*
* desc执行请求
*
* do 方法的改版返回结果由state 错误码和err 错误信息组成
*
* authorwh
* @param $param 以json格式提交数据
* @return array|bool|int|mixed|string
* @throws ApiException
* @throws \think\Exception
*/
function apiDo($param){
Tools::log_to_write_txt(['API OPERATE, IN',$this->post_url,$param], $this->getLogFileName());
$res = Tools::curl_post($this->post_url, json_encode($param));
Tools::log_to_write_txt(['API OPERATE, OUT',$this->post_url, $res], $this->getLogFileName());
if(empty($res['data'])) return $res;
$data = json_decode($res['data'], true);
if(is_null($data['state'])) {
Tools::log_to_write_txt([
'error: api操作,错误',
'post_url'=>$this->post_url,
'error_info'=>ApiException::API_RESPONSE_FORMAT_ERROR,
'error_sit'=>$this->getErrorSit(),
'result'=>$res,
], $this->getLogFileName());
throw new \Exception(ApiException::API_RESPONSE_FORMAT_ERROR);
}
$data['code'] = isset($data['state'])&&$data['state']==0?200:$data['state'];
$data['msg'] = isset($data['err'])?$data['err']:'api接口错误';
return $data;
}
/**
*
* desc执行请求
*
* 同框架、内部系统推荐
*
* authorwh
* @param $param 以表单方式提交数据
* @return array|bool|int|mixed|string
* @throws ApiException
* @throws \think\Exception
*/
function doPost($param){
Tools::log_to_write_txt(['API OPERATE, IN',$this->post_url,$param], $this->getLogFileName());
$res = Tools::curl_post($this->post_url, $param);
Tools::log_to_write_txt(['API OPERATE, OUT',$this->post_url, $res], $this->getLogFileName());
if(empty($res['data'])) return $res;
$data = json_decode($res['data'], true);
if(empty($data['code'])) {
Tools::log_to_write_txt([
'error: api操作,错误',
'post_url'=>$this->post_url,
'error_info'=>ApiException::API_RESPONSE_FORMAT_ERROR,
'error_sit'=>$this->getErrorSit(),
'result'=>$res,
], $this->getLogFileName());
throw new \Exception(ApiException::API_RESPONSE_FORMAT_ERROR);
}
return $data;
}
}

View File

@@ -0,0 +1,54 @@
<?php
/*
* description
* authorwh
* email
* createTime{2022/02/24} {15:01}
*/
namespace wanghua\general_utility_tools_php\api;
class BaseLibApi
{
/**
* desc创建目录
*
* authorwh
* @param $apidir
*/
function createDir($apidir){
if(!is_dir($apidir)){
mkdir($apidir,0777,true);
}
}
/**
* desc文件存在不写入
*
* authorwh
* @param $php_file
* @param $php
*/
function createFile($php_file,$php){
if(!file_exists($php_file)){
file_put_contents($php_file,$php);
}
}
/**
* desc追加文本
*
* authorwh
* @param $php_file
* @param $php
*/
function createFileExists($php_file,$php){
file_put_contents($php_file,$php,FILE_APPEND);
}
function ext(){
return '.php';
}
}

View File

@@ -0,0 +1,193 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/2/26} {10:54}
*/
namespace wanghua\general_utility_tools_php\api;
use app\common\model\TabConf;
use think\Db;
use wanghua\general_utility_tools_php\tool\Tools;
class BaseUserLogic
{
public $user_table = 'fa_users';
/**
* desc【通用】用户登录逻辑
*
* 请求端加密,服务端校验
*
* 签名规则
* 请求参数:
username //用户名
password //密码 md5(md5('a123456')) d477887b0636e5d87f79cc25c99d7dc9
timestamp //请求时间戳 2020-01-01 12:34:07
noncestr //随机字符串8位
sign //签名串,参考全站签名规则
other_params //其它接口的其它参数
* 全站sign规则
1. 对关联数组按照键升序排序
2. 拼接成字符串 keyvalkey2val2key3val3
3. 再2次md5生成32位小写串
4. 与请求参数一起发送到服务器
*
* authorwh
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
function tologin(){
if(!request()->isPost()){
return Tools::set_fail('错误');
}
Tools::log_to_write_txt(['登录入参:'=>input()]);
$input_data = input();
if(empty($input_data)){
return Tools::set_fail('请求参数错误');
}
if(empty($input_data['username'])){
return Tools::set_fail('用户名或密码错误');
}
if(empty($input_data['password'])){
return Tools::set_fail('用户名或密码错误.');
}
// start 这段代码可放在请求合法性里面校验 Tools::signature
if(empty($input_data['timestamp'])){
return Tools::set_fail('请求错误..');
}
if(empty($input_data['noncestr'])){
return Tools::set_fail('请求错误...');
}
if(empty($input_data['sign'])){
return Tools::set_fail('请求错误。');
}
$sign = $input_data['sign'];//md5后的字符串
unset($input_data['sign']);
//验签
$signstr = Tools::signature($input_data);
//Tools::log_to_write_txt([
// '签名日志:',
// $signstr,
// $sign,
// 'input'=>input(),
//]);
if($signstr != $sign){
return Tools::set_fail('非法请求');
}
//请求时间超过有效期 N分钟内有效
if(time()-5*60 > strtotime($input_data['timestamp'])){
return Tools::set_fail('请求失效');
}
// end 这段代码可放在请求合法性里面校验 Tools::signature
$username = $input_data['username'];
$user = Db::table($this->user_table)->where('username',$username)->find();
if(empty($user)){
return Tools::set_fail('账号密码错误!');
}
//校验密码
if($user['password'] != $input_data['password']){
return Tools::set_fail('账号密码错误!!');
}
$expires = time()+12*60*60;
//返回票据
$ticketstr = md5($user['username'].$user['expires']);
//保存ticket
$user[$ticketstr] = $expires;//N秒后过期
//修改有效期
Db::table($this->user_table)
->data([
'ticket'=>$ticketstr,
'expires'=>7*86400+time(),//7天
])
->where('username',$username)
->update();
session('api_user',$user);//备用(跨环境情况下session不生效)
return Tools::set_ok('登录成功',['ticket'=>$ticketstr]);
}
/**
* desc验证登录根据提交的ticket来校验是否登录适用于跨环境登录
*
* authorwh
* @return bool
*/
function isLogin(){
$ticket = input('ticket');
if(empty($ticket)){
Tools::log_to_write_txt(['title'=>'业务ticket字段不存在','input'=>input()]);
return false;
}
$user = Db::table(TabConf::$fa_users)
->where('ticket',$ticket)
->find();
if(empty($user)){
Tools::log_to_write_txt(['title'=>'用户未登录']);
return false;
}
//无效票据
//if(empty($user[$ticket])){
// Tools::log_to_write_txt(['title'=>'未获取到用户ticket',$ticket=>$user]);
//
// return false;
//}
//已过期
if(time() > $user['expires']){
Tools::log_to_write_txt(['title'=>'ticket已过期',$ticket=>$user]);
return false;
}
return true;
}
/**
* desc用户注册
*
* 此模块通用性不高,按需使用
*
* authorwh
*/
function reg($input_data){
try {
if(empty($input_data['username'])){
return Tools::set_fail('用户名错误');
}
if(empty($input_data['password'])){
return Tools::set_fail('密码错误.');
}
if(empty($input_data['password2'])){
return Tools::set_fail('密码错误!');
}
if($input_data['password'] != $input_data['password2']){
return Tools::set_fail('密码不一致!');
}
$insert_data = [
'username'=>$input_data['username'],
'password'=>$input_data['password'],
];
if(!empty($input_data['phone'])){
$insert_data['phone'] = $input_data['phone'];
}
Db::table($this->user_table)
->data($insert_data)
->insert();
return Tools::set_ok();
}catch (\Exception $e){
Tools::error_txt_log($e);
return Tools::set_fail();
}
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* description apk应用安装包信息读取
* authorwh
* email
* createTime{2020/7/1} {17:16}
*/
namespace wanghua\general_utility_tools_php\apk;
class Apk
{
/**
* 前提安装PHP ZIP扩展
* 扩展下载https://windows.php.net/downloads/pecl/releases/zip/
* desc读取APK
* authorwh
* @param string $path 文件地址
* @param bool $isRemote 是否远程文件
* @param string $proxy 代理
* @return array
*/
function read(string $path, $filename, bool $isRemote=false, $proxy=''){
/*解析安卓apk包中的压缩XML文件还原和读取XML内容
依赖功能需要PHP的ZIP包函数支持。*/
$appObj = new ApkParser();
$targetFile = $path;//a.apk; apk所在的路径地址
// 如果是远程文件,先下载到本地
if ($isRemote) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $path);
if ($proxy != '') {
curl_setopt($ch, CURLOPT_PROXY, $proxy);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 50);
$fileContent = curl_exec($ch);
curl_close($ch);
// 写入临时文件
$sys_tmp_path = tempnam(sys_get_temp_dir(), 'DL');
$fp = @fopen($sys_tmp_path, 'w+');
fwrite($fp, $fileContent);
$tmp_path = config('app.ROOT_PATH_PRO').'runtime/temp_file';
if(!file_exists($tmp_path)){
mkdir($tmp_path, 0777, true);
}
$targetFile = $tmp_path. '/' .time().$filename;
$res = move_uploaded_file($sys_tmp_path, $targetFile);
dump($res);die;
}
$result = [
'app_name'=>'',// 应用名称
'package'=>'',// 应用包名
'version_name'=>'',// 版本名称
'version_code'=>'',// 版本代码
];
dump($targetFile);die;
if(!is_file($targetFile)){
return $result;
}
$appObj->open($targetFile);//$res
$result = [
'app_name'=>$appObj->getAppName(),// 应用名称
'package'=>$appObj->getPackage(),// 应用包名
'version_name'=>$appObj->getVersionName(),// 版本名称
'version_code'=>$appObj->getVersionCode(),// 版本代码
];
return $result;
}
}

View File

@@ -0,0 +1,421 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/7/1} {17:12}
*/
namespace wanghua\general_utility_tools_php\apk;
use Exception;
class ApkParser{
//----------------------
// 公共函数,供外部调用
//----------------------
public function open($apk_file, $xml_file='AndroidManifest.xml'){
$zip = new \ZipArchive();
if ($zip->open($apk_file) === TRUE) {
$xml = $zip->getFromName($xml_file);
$zip->close();
if ($xml){
try {
return $this->parseString($xml);
}catch (Exception $e){
}
}
}
return false;
}
public function parseString($xml){
$this->xml = $xml;
$this->length = strlen($xml);
$this->root = $this->parseBlock(self::AXML_FILE);
return true;
}
public function getXML($node=NULL, $lv=-1){
if ($lv == -1) $node = $this->root;
if (!$node) return '';
if ($node['type'] == self::END_TAG) $lv--;
$xml = @($node['line'] == 0 || $node['line'] == $this->line) ? '' : "\n".str_repeat(' ', $lv);
$xml .= $node['tag'];
$this->line = @$node['line'];
foreach ($node['child'] as $c){
$xml .= $this->getXML($c, $lv+1);
}
return $xml;
}
public function getPackage(){
return $this->getAttribute('manifest', 'package');
}
public function getVersionName(){
return $this->getAttribute('manifest', 'android:versionName');
}
public function getVersionCode(){
return $this->getAttribute('manifest', 'android:versionCode');
}
public function getAppName(){
return $this->getAttribute('manifest/application', 'android:name');
}
public function getMainActivity(){
for ($id=0; true; $id++){
$act = $this->getAttribute("manifest/application/activity[{$id}]/intent-filter/action", 'android:name');
if (!$act) break;
if ($act == 'android.intent.action.MAIN') return $this->getActivity($id);
}
return NULL;
}
public function getActivity($idx=0){
$idx = intval($idx);
return $this->getAttribute("manifest/application/activity[{$idx}]", 'android:name');
}
public function getAttribute($path, $name){
$r = $this->getElement($path);
if (is_null($r)) return NULL;
if (isset($r['attrs'])){
foreach ($r['attrs'] as $a){
if ($a['ns_name'] == $name) return $this->getAttributeValue($a);
}
}
return NULL;
}
//----------------------
// 类型常量定义
//----------------------
const AXML_FILE = 0x00080003;
const STRING_BLOCK = 0x001C0001;
const RESOURCEIDS = 0x00080180;
const START_NAMESPACE = 0x00100100;
const END_NAMESPACE = 0x00100101;
const START_TAG = 0x00100102;
const END_TAG = 0x00100103;
const TEXT = 0x00100104;
const TYPE_NULL =0;
const TYPE_REFERENCE =1;
const TYPE_ATTRIBUTE =2;
const TYPE_STRING =3;
const TYPE_FLOAT =4;
const TYPE_DIMENSION =5;
const TYPE_FRACTION =6;
const TYPE_INT_DEC =16;
const TYPE_INT_HEX =17;
const TYPE_INT_BOOLEAN =18;
const TYPE_INT_COLOR_ARGB8 =28;
const TYPE_INT_COLOR_RGB8 =29;
const TYPE_INT_COLOR_ARGB4 =30;
const TYPE_INT_COLOR_RGB4 =31;
const UNIT_MASK = 15;
private static $RADIX_MULTS = array(0.00390625, 3.051758E-005, 1.192093E-007, 4.656613E-010);
private static $DIMENSION_UNITS = array("px","dip","sp","pt","in","mm","","");
private static $FRACTION_UNITS = array("%","%p","","","","","","");
private $xml='';
private $length = 0;
private $stringCount = 0;
private $styleCount = 0;
private $stringTab = array();
private $styleTab = array();
private $resourceIDs = array();
private $ns = array();
private $cur_ns = NULL;
private $root = NULL;
private $line = 0;
//----------------------
// 内部私有函数
//----------------------
private function getElement($path){
if (!$this->root) return NULL;
$ps = explode('/', $path);
$r = $this->root;
foreach ($ps as $v){
if (preg_match('/([^\[]+)\[([0-9]+)\]$/', $v, $ms)){
$v = $ms[1];
$off = $ms[2];
}else {
$off = 0;
}
foreach ($r['child'] as $c){
if ($c['type'] == self::START_TAG && $c['ns_name'] == $v){
if ($off == 0){
$r = $c; continue 2;
}else {
$off--;
}
}
}
// 没有找到节点
return NULL;
}
return $r;
}
private function parseBlock($need = 0){
$o = 0;
$type = $this->get32($o);
if ($need && $type != $need) throw new Exception('Block Type Error', 1);
$size = $this->get32($o);
if ($size < 8 || $size > $this->length) throw new Exception('Block Size Error', 2);
$left = $this->length - $size;
$props = false;
switch ($type){
case self::AXML_FILE:
$props = array(
'line' => 0,
'tag' => '<?xml version="1.0" encoding="utf-8"?>'
);
break;
case self::STRING_BLOCK:
$this->stringCount = $this->get32($o);
$this->styleCount = $this->get32($o);
$o += 4;
$strOffset = $this->get32($o);
$styOffset = $this->get32($o);
$strListOffset = $this->get32array($o, $this->stringCount);
$styListOffset = $this->get32array($o, $this->styleCount);
$this->stringTab = $this->stringCount > 0 ? $this->getStringTab($strOffset, $strListOffset) : array();
$this->styleTab = $this->styleCount > 0 ? $this->getStringTab($styOffset, $styListOffset) : array();
$o = $size;
break;
case self::RESOURCEIDS:
$count = $size / 4 - 2;
$this->resourceIDs = $this->get32array($o, $count);
break;
case self::START_NAMESPACE:
$o += 8;
$prefix = $this->get32($o);
$uri = $this->get32($o);
if (empty($this->cur_ns)){
$this->cur_ns = array();
$this->ns[] = &$this->cur_ns;
}
$this->cur_ns[$uri] = $prefix;
break;
case self::END_NAMESPACE:
$o += 8;
$prefix = $this->get32($o);
$uri = $this->get32($o);
if (empty($this->cur_ns)) break;
unset($this->cur_ns[$uri]);
break;
case self::START_TAG:
$line = $this->get32($o);
$o += 4;
$attrs = array();
$props = array(
'line' => $line,
'ns' => $this->getNameSpace($this->get32($o)),
'name' => $this->getString($this->get32($o)),
'flag' => $this->get32($o),
'count' => $this->get16($o),
'id' => $this->get16($o)-1,
'class' => $this->get16($o)-1,
'style' => $this->get16($o)-1,
'attrs' => &$attrs
);
$props['ns_name'] = $props['ns'].$props['name'];
for ($i=0; $i < $props['count']; $i++){
$a = array(
'ns' => $this->getNameSpace($this->get32($o)),
'name' => $this->getString($this->get32($o)),
'val_str' => $this->get32($o),
'val_type' => $this->get32($o),
'val_data' => $this->get32($o)
);
$a['ns_name'] = $a['ns'].$a['name'];
$a['val_type'] >>= 24;
$attrs[] = $a;
}
// 处理TAG字符串
$tag = "<{$props['ns_name']}";
foreach ($this->cur_ns as $uri => $prefix){
$uri = $this->getString($uri);
$prefix = $this->getString($prefix);
$tag .= " xmlns:{$prefix}=\"{$uri}\"";
}
foreach ($props['attrs'] as $a){
$tag .= " {$a['ns_name']}=\"".
$this->getAttributeValue($a).
'"';
}
$tag .= '>';
$props['tag'] = $tag;
unset($this->cur_ns);
$this->cur_ns = array();
$this->ns[] = &$this->cur_ns;
$left = -1;
break;
case self::END_TAG:
$line = $this->get32($o);
$o += 4;
$props = array(
'line' => $line,
'ns' => $this->getNameSpace($this->get32($o)),
'name' => $this->getString($this->get32($o))
);
$props['ns_name'] = $props['ns'].$props['name'];
$props['tag'] = "</{$props['ns_name']}>";
if (count($this->ns) > 1){
array_pop($this->ns);
unset($this->cur_ns);
$this->cur_ns = array_pop($this->ns);
$this->ns[] = &$this->cur_ns;
}
break;
case self::TEXT:
$o += 8;
$props = array(
'tag' => $this->getString($this->get32($o))
);
$o += 8;
break;
default:
throw new Exception('Block Type Error', 3);
break;
}
$this->skip($o);
$child = array();
while ($this->length > $left){
$c = $this->parseBlock();
if ($props && $c) $child[] = $c;
if ($left == -1 && $c['type'] == self::END_TAG){
$left = $this->length;
break;
}
}
if ($this->length != $left) throw new Exception('Block Overflow Error', 4);
if ($props){
$props['type'] = $type;
$props['size'] = $size;
$props['child'] = $child;
return $props;
}else {
return false;
}
}
private function getAttributeValue($a){
$type = &$a['val_type'];
$data = &$a['val_data'];
switch ($type){
case self::TYPE_STRING:
return $this->getString($a['val_str']);
case self::TYPE_ATTRIBUTE:
return sprintf('?%s%08X', self::_getPackage($data), $data);
case self::TYPE_REFERENCE:
return sprintf('@%s%08X', self::_getPackage($data), $data);
case self::TYPE_INT_HEX:
return sprintf('0x%08X', $data);
case self::TYPE_INT_BOOLEAN:
return ($data != 0 ? 'true' : 'false');
case self::TYPE_INT_COLOR_ARGB8:
case self::TYPE_INT_COLOR_RGB8:
case self::TYPE_INT_COLOR_ARGB4:
case self::TYPE_INT_COLOR_RGB4:
return sprintf('#%08X', $data);
case self::TYPE_DIMENSION:
return $this->_complexToFloat($data).self::$DIMENSION_UNITS[$data & self::UNIT_MASK];
case self::TYPE_FRACTION:
return $this->_complexToFloat($data).self::$FRACTION_UNITS[$data & self::UNIT_MASK];
case self::TYPE_FLOAT:
return $this->_int2float($data);
}
if ($type >=self::TYPE_INT_DEC && $type < self::TYPE_INT_COLOR_ARGB8){
return (string)$data;
}
return sprintf('<0x%X, type 0x%02X>', $data, $type);
}
private function _complexToFloat($data){
return (float)($data & 0xFFFFFF00) * self::$RADIX_MULTS[($data>>4) & 3];
}
private function _int2float($v) {
$x = ($v & ((1 << 23) - 1)) + (1 << 23) * ($v >> 31 | 1);
$exp = ($v >> 23 & 0xFF) - 127;
return $x * pow(2, $exp - 23);
}
private static function _getPackage($data){
return ($data >> 24 == 1) ? 'android:' : '';
}
private function getStringTab($base, $list){
$tab = array();
foreach ($list as $off){
$off += $base;
$len = $this->get16($off);
$mask = ($len >> 0x8) & 0xFF;
$len = $len & 0xFF;
if ($len == $mask){
if ($off + $len > $this->length) throw new Exception('String Table Overflow', 11);
$tab[] = substr($this->xml, $off, $len);
}else {
if ($off + $len * 2 > $this->length) throw new Exception('String Table Overflow', 11);
$str = substr($this->xml, $off, $len * 2);
$tab[] = mb_convert_encoding($str, 'UTF-8', 'UCS-2LE');
}
}
return $tab;
}
private function getString($id){
if ($id > -1 && $id < $this->stringCount){
return $this->stringTab[$id];
}else {
return '';
}
}
private function getNameSpace($uri){
for ($i=count($this->ns); $i > 0; ){
$ns = $this->ns[--$i];
if (isset($ns[$uri])){
$ns = $this->getString($ns[$uri]);
if (!empty($ns)) $ns .= ':';
return $ns;
}
}
return '';
}
private function get32(&$off){
$int = unpack('V', substr($this->xml, $off, 4));
$off += 4;
return array_shift($int);
}
private function get32array(&$off, $size){
if ($size <= 0) return NULL;
$arr = unpack('V*', substr($this->xml, $off, 4 * $size));
if (count($arr) != $size) throw new Exception('Array Size Error', 10);
$off += 4 * $size;
return $arr;
}
private function get16(&$off){
$int = unpack('v', substr($this->xml, $off, 2));
$off += 2;
return array_shift($int);
}
private function skip($size){
$this->xml = substr($this->xml, $size);
$this->length -= $size;
}
}

View File

@@ -0,0 +1,103 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/3/19} {13:57}
*/
namespace wanghua\general_utility_tools_php\coin;
use wanghua\general_utility_tools_php\http\Curl;
class Coin
{
public $apiKey = 'f94a2a9c-28be-47c3aaa-9c29-1e83b309d076';
public $url = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest';
/**
* desc
'limit'=>'1000',
'sort'=>'market_cap',
'sort_dir'=>'desc',
* @param array $params
* @return array|bool|int|string
*/
function getAll(array $params){
$params_str = http_build_query($params);
$url = $this->url.'?'.$params_str;
$header = [
'X-CMC_PRO_API_KEY:'.$this->apiKey,
'accept: application/json',
];
return Curl::curl_get($url,10,$header);
}
/**
* desc
*
*https://coinmarketcap.com/api/documentation/v1/#section/Introduction
* 'https://pro-api.coinmarketcap.com/v2/cryptocurrency/ohlcv/historical'
* authorwh
*/
function k(){
$url = $this->url;
$time_start = input('time_start');
$time_end = input('time_end');
$type = input('type');
$header = [
'X-CMC_PRO_API_KEY:'.$this->apiKey,
];
$arr = explode('=',$type);
$params = [
$arr[0]=>$arr[1],//币种
'time_start'=>$time_start,
'time_end'=>$time_end,
'count'=>7,
];
$url = $url.'?'.http_build_query($params);
return Curl::curl_get($url,10,$header);
}
private function curl_request($url, $method = 'GET',$data=null,$header=array(),$call_back=null)
{
set_time_limit(30);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
if($header){
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
if($method == 'POST'){
if($data) curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
if($call_back){
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $call_back);
}
$result = curl_exec($ch);
if (curl_errno($ch)) {
return [
'status' => 'error',
'message' => 'curl 错误信息: ' . curl_error($ch)
];
}
curl_close($ch);
return $result;
}
}

View File

@@ -0,0 +1,390 @@
<?php
/*
* description
* authorwh
* email
* createTime{2019/12/17} {14:30}
*/
namespace wanghua\db\general_utility_tools_php;
use wanghua\db\general_utility_tools_php\SqlOperateLog;
use think\Container;
use think\Db;
use think\Paginator;
/**
* 环境要求:
* ThinkPHP5.1+,PHP7.0+,MySQL8.0+
*
*
* 查询缓存器、监控器
* 【使用框架链式操作时,配合此类才能发挥完美效果】
* 举例
* function getMemberInIds(string $ids){
$obj = Db::name($this->table)->whereIn("id",$ids)->where(["is_deleted"=>0]);
return DbCacheUtility::getAll($obj);
}
* Class DbCacheUtility
* @package libraries
*/
class DbCacheUtility
{
const NoCacheTime = 0;
const LowCacheTime = 60 * 1;
const NormalCacheTime = 60 * 10;
const HighCacheTime = 60 * 60;
const LongCacheTime = 60 * 60 * 24;
/**
* desc
* authorwh
* @param $object 查询对象
* @param int $cacheDuration
* @param bool $is_log
* @return mixed
*/
public static function getOne($object, $cacheDuration = self::NoCacheTime, $is_log = true)
{
$begin_time = Tools::getMillisecond();
$is_cache = 0;
$last_sql = $object->fetchSql(true)->find();
$md5_name = md5($last_sql);
$key = 'get_one:' . $md5_name;
if ($cacheDuration == 0) {
if(cache($key)){
cache($key, null);
}
}
if (!(cache($key))) {
$result = $object->fetchSql(false)->find();
if ($cacheDuration != 0) {
cache($key, $result, $cacheDuration);
}
} else {
$is_cache = 1;
$result = cache($key);
}
$end_time = Tools::getMillisecond();
if($is_log){
SqlOperateLog::add($last_sql, $end_time, $begin_time, $md5_name, 'get_one', $is_cache);
}
return $result;
}
/**
* desc
* authorwh
* @param $object 查询对象
* @param int $cacheDuration
* @param bool $is_log
* @return mixed
*/
public static function getAll($object, $cacheDuration = self::NoCacheTime, $is_log = true)
{
$begin_time = Tools::getMillisecond();
$is_cache = 0;
$last_sql = $object->fetchSql(true)->select();
$md5_name = md5($last_sql);
$key = 'get_all:' . $md5_name;
if ($cacheDuration == 0) {
if(cache($key)){
cache($key, null);
}
}
if (!(cache($key))) {
$result = $object->fetchSql(false)->select();
if ($cacheDuration != 0) {
cache($key, $result, $cacheDuration);
}
} else {
$is_cache = 1;
$result = cache($key);
}
$end_time = Tools::getMillisecond();
if($is_log){
SqlOperateLog::add($last_sql, $end_time, $begin_time, $md5_name, 'get_all', $is_cache);
}
return $result;
}
/**
* desc
* authorwh
* @param $object
* @param int $page
* @param int $listRows
* @param bool $simple
* @param array $config
* @param int $cacheDuration
* @param bool $is_log
* @return mixed
*/
static function paginates($object, $page=1, $listRows = null, $simple = false, $config = [], $cacheDuration = self::NoCacheTime, $is_log = true){
$begin_time = Tools::getMillisecond();
$is_cache = 0;
$last_sql = $object->limit(abs(1*$page-1) * $listRows, $listRows)->fetchSql(true)->select();
$md5_name = md5($last_sql);
$key = 'paginates:' . $md5_name;
if ($cacheDuration == 0) {
if(cache($key)){
cache($key, null);
}
}
if (!(cache($key))) {
$result = $object->fetchSql(false)->paginate($listRows, $simple, $config);
if ($cacheDuration != 0) {
cache($key, $result, $cacheDuration);
}
} else {
$is_cache = 1;
$result = cache($key);
}
$end_time = Tools::getMillisecond();
if($is_log){
SqlOperateLog::add($last_sql, $end_time, $begin_time, $md5_name, 'paginates', $is_cache);
}
return $result;
}
/**
* desc
* authorwh
* @param $sql
* @param array $bind
* @return mixed
* @throws \think\db\exception\BindParamException
* @throws \think\exception\PDOException
*/
static function queryScalar($sql, $bind = [], $field, $cacheDuration = self::NoCacheTime, $is_log = true)
{
$begin_time = Tools::getMillisecond();
$is_cache = 0;
$last_sql = self::presetBind($sql, $bind);
$md5_name = md5($last_sql);
$key = 'query_scalar:' . $md5_name;
if ($cacheDuration == 0) {
if(cache($key)){
cache($key, null);
}
}
if (!(cache($key))) {
$result = Db::query($sql, $bind);
if ($cacheDuration != 0) {
cache($key, $result, $cacheDuration);
}
} else {
$is_cache = 1;
$result = cache($key);
}
$end_time = Tools::getMillisecond();
if($is_log){
SqlOperateLog::add($last_sql, $end_time, $begin_time, $md5_name, 'query_scalar', $is_cache);
}
return $result ? $result[0][$field] : [];
}
/**
* desc
* authorwh
* @param $sql
* @param array $bind
* @return mixed
* @throws \think\db\exception\BindParamException
* @throws \think\exception\PDOException
*/
static function queryOne($sql, $bind = [], $cacheDuration = self::NoCacheTime, $is_log = true)
{
$begin_time = Tools::getMillisecond();
$is_cache = 0;
$last_sql = self::presetBind($sql, $bind);
$md5_name = md5($last_sql);
$key = 'query_one:' . $md5_name;
if ($cacheDuration == 0) {
if(cache($key)){
cache($key, null);
}
}
if (!(cache($key))) {
$result = Db::query($sql, $bind);
if ($cacheDuration != 0) {
cache($key, $result, $cacheDuration);
}
} else {
$is_cache = 1;
$result = cache($key);
}
$end_time = Tools::getMillisecond();
if($is_log){
SqlOperateLog::add($last_sql, $end_time, $begin_time, $md5_name, 'query_one', $is_cache);
}
return $result ? $result[0] : [];
}
/**
* desc
* authorwh
* @param $sql
* @param array $bind
* @return mixed
* @throws \think\db\exception\BindParamException
* @throws \think\exception\PDOException
*/
static function queryAll($sql, $bind = [], $cacheDuration = self::NoCacheTime, $is_log = true)
{
$begin_time = Tools::getMillisecond();
$is_cache = 0;
$last_sql = self::presetBind($sql, $bind);
//echo $last_sql;die;
$md5_name = md5($last_sql);
$key = 'query_all:' . $md5_name;
if ($cacheDuration == 0) {
if(cache($key)){
cache($key, null);
}
}
if (!(cache($key))) {
$result = Db::query($sql, $bind);
if ($cacheDuration != 0) {
cache($key, $result, $cacheDuration);
}
} else {
$is_cache = 1;
$result = cache($key);
}
$end_time = Tools::getMillisecond();
if($is_log){
SqlOperateLog::add($last_sql, $end_time, $begin_time, $md5_name, 'query_all', $is_cache);
}
return $result;
}
/**
* desc
* authorwh
* @param $sql
* @param array $bind
* @return int
* @throws \think\db\exception\BindParamException
* @throws \think\exception\PDOException
*/
static function execute($sql, $bind = []){
return Db::execute($sql, $bind);
}
/**
* desc
* authorwh
* @param $sql
* @param array $bind
* @return mixed
*/
static function presetBind($sql, $bind = []){
if($bind){
foreach ($bind as $key=>$val){
$sql = str_replace(':'.$key, $val, $sql);
}
}
return $sql;
}
static function paginate($sql, $bind, $total, $listRows = null, $simple = false, $config = [])
{
//if (is_int($simple)) {
// $total = $simple;
// $simple = false;
//}
$paginate = Container::get('config')->pull('paginate');
if (is_array($listRows)) {
$config = array_merge($paginate, $listRows);
$listRows = $config['list_rows'];
} else {
$config = array_merge($paginate, $config);
$listRows = $listRows ?: $config['list_rows'];
}
/** @var Paginator $class */
$class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']);
$page = isset($config['page']) ? (int) $config['page'] : call_user_func([
$class,
'getCurrentPage',
], $config['var_page']);
$page = $page < 1 ? 1 : $page;
$config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, 'getCurrentPath']);
//if (!isset($total) && !$simple) {
//
//
// unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
//
// $bind = $this->bind;
// $total = $this->count();
// $results = $this->options($options)->bind($bind)->page($page, $listRows)->select();
//} elseif ($simple) {
// $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
// $total = null;
//} else {
// $results = $this->page($page, $listRows)->select();
//}
$results = DbCacheUtility::queryAll($sql, $bind);
return $class::make($results, $listRows, $page, $total, $simple, $config);
}
}

View File

@@ -0,0 +1,109 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/5/22} {16:43}
*/
namespace wanghua\db\general_utility_tools_php;
use think\cache\driver\Redis as RedisDriver;
use think\Db;
use think\facade\Cache;
/**
* Redis操作工具
*
* 环境要求:
* ThinkPHP5.1+,PHP7.0+,MySQL8.0+
*
*
* Class RedisUtility
* @package libraries
*/
class RedisUtility
{
protected static $redisHandle = null;
/**
* desc初始化Redis对象
* authorwh
* @return object|null
*/
static function redisObject(){
if(empty(self::$redisHandle)){
//初始化驱动拿到句柄
self::$redisHandle = (new RedisDriver(config('cache.redis')))->handler();
}
return self::$redisHandle;
}
/**
* desc设置 0 永久有效
* authorwh
* @param $key_name 键名
* @param $value 值 json数据
* @return bool
*/
static function set($key_name, $value){
return Cache::store('redis')->set($key_name, json_encode($value, JSON_UNESCAPED_UNICODE));
}
/**
* desc重置
* authorwh
* @param $key_name
* @return bool
*/
static function reset($key_name){
return Cache::store('redis')->set($key_name, null);
}
/**
* desc设置过期时间
* authorwh
* @param $key_name 键名
* @param $value 值 json数据
* @param int $expire 过期时间 0 永久有效
* @return bool
*/
static function setExpire($key_name, $value, $expire=0){
return Cache::store('redis')->set($key_name, json_encode($value), $expire);
}
/**
* desc存储hash类型
* authorwh
* @param $key
* @param $field
* @param $value
*/
static function hSet($key, $field, $value){
return self::redisObject()->hSet($key, $field, json_encode($value, JSON_UNESCAPED_UNICODE));
}
/**
* desc删除指定的键。
* authorwh
* @param $key
* @return mixed
*/
static function delete($key){
return self::redisObject()->delete($key);
}
/**
* desc从存储在键上的哈希中移除一个值。
* 如果哈希表不存在,或者键不存在,则返回 FALSE 。
* authorwh
* @param $key
* @param $hashKey1
* @return mixed
*/
static function hDel($key, $hashKey1){
return self::redisObject()->hDel($key, $hashKey1);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* description
* authorwh
* email
* createTime{2019/10/31} {9:25}
*/
namespace wanghua\db\general_utility_tools_php;
use think\Db;
use think\facade\Log;
/**
* 环境要求:
* ThinkPHP5.1+,PHP7.0+,MySQL8.0+
*
* 日志操作类配合DbCacheUtility.php使用。
* 使用前先在系统config.php文件中添加配置:
* 1、is_sql_slow_log:是否记录sql慢日志true or false
* 2、新建表log_sql_operate字段action,is_cache,duration_time,type,is_cache,sql,md5_name
* 3、如果什么错都没有报但是程序就是没反应则去runtime目录中找错误。
*
* Class SqlOperateLog
* @package wanghua\general_utility_tools_php
*/
class SqlOperateLog
{
static function add($sql,$end_time,$begin_time,$md5_name,$type,$is_cache){
$data = [
'action'=>request()->baseUrl(),
'duration_time'=>$end_time - $begin_time,
'type'=>$type,
'is_cache'=>$is_cache?1:0,
'sql'=>$sql,
'md5_name'=>$md5_name,
];
try{
if(config('app.is_sql_slow_log') && ($end_time - $begin_time) >= config('app.is_sql_slow_log')){
Db::table('log_sql_operate')->insert($data);
}
}catch (\Exception $e){
//DATABASE BOOM
//tp6
//Log::error('========[数据库异常:DATABASE BOOM]========'.$e->getTraceAsString());
//Log::close();
//tp5.1
Log::write('========[DATABASE ERROR:DATABASE BOOM]========【'.$e->getMessage().'】'.$e->getTraceAsString());
}
}
}

View File

@@ -0,0 +1,262 @@
<?php
/*
* description
* authorwh
* email
* createTime{2021/4/26} {9:45}
*/
namespace wanghua\general_utility_tools_php\db\es;
use wanghua\general_utility_tools_php\Date;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* 【es原生操作库】
* 【仅支持框架ThinkPHP5+、 PHP7.2+】
*
* BaseElasticsearch.php类库的替代库
*
* 使用步骤:
* 初始化设置ip、索引前缀-普通索引请设置为空字符串)
* 设置索引范围例如查询本月如果索引不是按日期规律设置请使用普通索引设置方法setIndexNormal并设置前缀为空字符串
* 设置查询方法eg: _search
* 设置查询参数(查询条件)
* 执行
* 返回结果
* Class ElasticsearchUtility
* @package app\admin\logic
*/
class Elasticsearch
{
protected $es_log_file = 'es_log';
//最终拼装的请求地址
protected $post_url = ''; //eg: $url = 'http://49.4.3.4:9111/qa-item-2021.04/_search';
//查询参数
protected $query_param = [];
//查询方法
protected $query_method = '_search';
//文档索引
protected $index = '';
//服务器ip
protected $ip = '';
//索引前缀(优先设置)
protected $index_sign = '';//eg: qa-stat-
//设置查询日期格式符号
protected $doc_date_sign = '.';
//设置是哪个方法调用本es库用于日志记录
protected $exe_func = '';
/**
* ElasticsearchUtility constructor.
* @param string $ip_port 协议+服务器ip+端口号,必须
* @param string $index_sign 当es索引为有规律的日期索引时可使用前缀并配合日期索引设置方法体验更佳
*/
public function __construct(string $ip_port, string $index_sign='')
{
$this->ip = false===strpos($ip_port,'http')?'http://'.$ip_port:$ip_port;
$this->index_sign = $index_sign;
}
/**
* desc设置查询方法默认_search
* authorwh
* @param $method_name
*/
function setQueryMethod($method_name){
$this->query_method = $method_name;
}
/**
* desc根据日期索引查询文档
* 注:默认一个月
*
* eg:'qa-stat-2021.01';
*
* 注如果es设置的索引没有规律也不是日期作为索引请设置普通索引setIndexNormal且请在初始化时设置索引前缀index_sign为""字符串
* authorwh
*/
function setIndexDefault(){
$this->setRequestIndex(date('Y'.$this->doc_date_sign.'m'));
}
/**
* desc设置要查询的日期文档索引
*
* 1、按年检索文档[建议5年左右]
*
* eg: qa-stat-2018.*,qa-stat-2019.*,qa-stat-2020.*,qa-stat-2021.*
*
* 注意当索引过长es会抛出索引太长异常
*
* 注如果es设置的索引没有规律也不是日期作为索引请设置普通索引setIndexNormal且请在初始化时设置索引前缀index_sign为""字符串
*
* authorwh
* @param string $start_time 开始时间 eg2010-01-01 08:05:55
* @param string $end_time 结束时间 eg2021-12-31 12:05:00
* @return string
*/
function setIndexYearDate(string $start_time, string $end_time){
//默认查询索引
$index = date('Y'.$this->doc_date_sign.'*', strtotime($start_time));
$sign = ',';//分隔符
$m = date('Y', strtotime($end_time)) - date('Y', strtotime($start_time));
//拼装查询索引
for ($i=0; $i<$m; $i++){
$index .= $sign.($this->index_sign.(date('Y', strtotime($start_time))+$i).$this->doc_date_sign.'*');
}
$this->setRequestIndex($index);
}
/**
* desc设置日期索引
*
* 检索全部日期索引文档
*
* 例如es设置的索引为
* qa-stat-2021.01
qa-stat-2021.02
qa-stat-2021.03
qa-stat-2021.04
*
* 实际查询自动设置为qa-stat-*
*
* 注如果es设置的索引没有规律也不是日期作为索引请设置普通索引setIndexNormal且请在初始化时设置索引前缀index_sign为""字符串
*
* authorwh
* @return string
*/
function setIndexAllDate(){
$this->setRequestIndex('*');
}
/**
* desc设置普通索引
*
* 调用此方法请在初始化时设置索引前缀index_sign为""空字符串
*
* authorwh
* @param string $index
*/
function setIndexNormal(string $index){
$this->setRequestIndex($index);
}
/**
* desc设置跨月份索引
*
* eg: qa-stat-2021.01,qa-stat-2021.02
*
* 注如果es设置的索引没有规律也不是日期作为索引请设置普通索引setIndexNormal且请在初始化时设置索引前缀index_sign为""字符串
*
* authorwh
* @param string $start_time eg:'2020-11'
* @param string $end_time eg:'2021-01'
* @return string eg: qa-stat-2020.11,qa-stat-2020.12,qa-stat-2021.01
*/
function setIndexDate(string $start_time, string $end_time){
$date = new Date();
//计算月份
$m = $date->dateCutMonth($start_time, $end_time);
////解决跨年且不足1个月时索引设置错误问题
//if($m == 0 && (date('Y', strtotime($end_time)) > date('Y', strtotime($start_time)))){
// $m = 1;
//}
//格式
$date->date_format = 'Y-m';
//默认查询索引
$index = date('Y'.$this->doc_date_sign.'m', strtotime($start_time));
$tmp_time = $start_time;
$sign = ',';//分隔符
//拼装查询索引
for ($i=0; $i<$m; $i++){
$tmp_time = $date->addTime(1, 'M', strtotime($tmp_time));
$index .= $sign.$this->index_sign.(date('Y'.$this->doc_date_sign.'m', strtotime($tmp_time)));
}
$this->setRequestIndex($index);
}
/**
* desc设置查询参数
* authorwh
* @param string $query_param_json
*/
function setQueryParam(string $query_param_json){
$this->query_param = $query_param_json;
$this->post_url = $this->ip.'/'.$this->index.'/'.$this->query_method;
}
/**
* desc获取查询参数用于调试
* authorwh
* @return array
*/
function getQueryParam(){
return [
'ip'=>$this->ip,
'index_sign'=>$this->index_sign,
'index'=>$this->index,
'query_method'=>$this->query_method,
'post_url'=>$this->post_url,
'query_param'=>$this->query_param,
];
}
/**
* desc执行es查询
* authorwh
* @return array|bool|int|string
* @throws \Exception
*/
function execute(){
if(empty($this->ip)) throw new \Exception('请设置ip');
//if(empty($this->index_sign)) throw new \Exception('请设置索引前缀');
if(empty($this->index)) throw new \Exception('请设置索引');
if(empty($this->query_method)) throw new \Exception('请设置查询方法');
if(empty($this->query_param)) throw new \Exception('请设置查询参数');
Tools::log_to_write_txt(['exe_func'=>$this->exe_func,'request_url'=>$this->post_url,'IN'=>" | IN: ",'query_params'=>$this->query_param, 'input'=>input()], $this->es_log_file);
$result = Tools::curl_post($this->post_url, $this->query_param);
Tools::log_to_write_txt(['exe_func'=>$this->exe_func,'OUT'=>" | OUT: ", 'result'=>$result], $this->es_log_file);
return $result;
}
/**
* desc设置是哪个方法调用本es库用于日志记录
* authorwh
* @param string $function
*/
function setExecuteFunction(string $function){
$this->exe_func = $function;
}
/**
* desc设置请求文档索引
* authorwh
* @param string $index
*/
protected function setRequestIndex(string $index){
$this->index = $this->index_sign.$index;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
## 仅适用于tp5+, php7+
## 表管理
## 字段管理

View File

@@ -0,0 +1,304 @@
<?php
/*
* description
* authorwh
* email
* createTime{2022/02/24} {10:57}
*/
namespace wanghua\general_utility_tools_php\db\mysql\lib;
use think\Db;
class Field
{
/**
* desc创建字段存在则修改字段
* authorwh
* @param array $table
*/
function createfield(array $table){
$table_name = $table['tablename'];
//新增字段
$dec_num = '';
if(in_array(strtoupper($table['type']), ['TINYINT','SMALLINT','MEDIUMINT','INT','BIGINT','FLOAT','DOUBLE','DECIMAL'])){
$table['default'] = '0';//整型默认值 浮点类型自动转为0.00
if(in_array(strtoupper($table['type']), ['FLOAT','DOUBLE','DECIMAL'])){
$dec_num = ','.$table['decimals_size'];//浮点类型小数位位数
}
}elseif ($table['type'] == 'enum'){
//枚举值不转换
}else{
$table['default'] = '""';
}
if(in_array(strtolower($table['type']), ['timestamp','datetime','date','time','text','mediumtext','longtext'])){
$table['size'] = 0;//重写 默认0
$table['default'] = 'null';//重写
}
//是否可以负数
if(in_array(strtolower($table['type']), ['int','tinyint','bigint','smallint', 'double', 'float', 'decimal']) && !empty($post['is_unsigned'])){//不是
$is_unsigned = 'unsigned';
}else{//是 表单可选值
$is_unsigned = '';
}
if (in_array($table['fields_name'], ['create_time', 'update_time'])){
if($table['fields_name'] == 'create_time'){
$table['default'] = 'CURRENT_TIMESTAMP';
}
if($table['fields_name'] == 'update_time'){
$table['default'] = 'NULL ON UPDATE CURRENT_TIMESTAMP';
}
}
//验证字段是否存在
$f = DB::table($table_name)->getTableFields();
if(in_array($table['fields_name'], $f)){
//枚举类型
if ($table['type'] == 'enum'){
$enum_str = '';
$enum_default = '';
//组合枚举值
if($table['default']){
$enum_str.='ENUM('.$table['default'].')';
$enum_default = explode(',', $table['default'])[0];
}
$sql2 = "ALTER TABLE {$table_name} MODIFY {$table['fields_name']} {$enum_str} NOT NULL DEFAULT {$enum_default} COMMENT '{$table['title']}';";
}else{
$sql2 = "ALTER TABLE {$table_name} MODIFY COLUMN {$table['fields_name']} {$table['type']}(".$table['size'].$dec_num.") {$is_unsigned} DEFAULT {$table['default']} COMMENT '{$table['title']}';";
}
}else{
if(in_array($table['type'], ['text','longtext'])){
$sql2 = "ALTER TABLE {$table_name} ADD COLUMN {$table['fields_name']} {$table['type']} COMMENT '{$table['title']}' AFTER id;";
}elseif ($table['type'] == 'enum'){//枚举类型
$enum_str = '';
$enum_default = '';
//组合枚举值
if($table['default']){
$enum_str.='ENUM('.$table['default'].')';
$enum_default = explode(',', $table['default'])[0];
}
$sql2 = "ALTER TABLE {$table_name} ADD {$table['fields_name']} {$enum_str} NOT NULL DEFAULT {$enum_default} COMMENT '{$table['title']}' AFTER id;";
}else{
$sql2 = "ALTER TABLE {$table_name} ADD COLUMN {$table['fields_name']} {$table['type']}(".$table['size'].$dec_num.") {$is_unsigned} DEFAULT {$table['default']} COMMENT '{$table['title']}' AFTER id;";
}
}
DB::execute($sql2);
}
/**
* desc删除字段
* authorwh
* @param $tablename
* @param $fieldname
*/
function dropFieldName($tablename, $fieldname){
$sql = "ALTER TABLE {$tablename} DROP COLUMN {$fieldname};";
DB::execute($sql);
}
/**
* desc改注释
* authorwh
* @param $tablename
* @param $comment
* @return bool
*/
function updateComment($tablename,$field, $comment){
$sql = "ALTER TABLE {$tablename} MODIFY COLUMN {$field} INT COMMENT '{$comment}';";
Db::execute($sql);
}
/**
* desc获取数据表中所有字段的数据类型含其它属性
*
* 查询结果字段名说明
*
* F_FIELD字段名称
* F_DATATYPE数据类型
* F_DATALENGTH数据长度int类型默认为null,业务处理时默认为10即可
* F_PRECISION精度
* F_DECIMAL_DIGITS小数位数
* F_ALLOWNULL是否允许为null值(1是0否)
* F_FIELDNAME字段名称
* F_PRIMARYKEY是否主键(1是0否)
* F_DEFAULTS字段默认值
*
* authorwh
* @param string $dbname
* @param string $tablename
* @param string $fieldname
* @return mixed
*/
function getFieldsDataType(string $dbname,string $tablename){
$sql = "SELECT
COLUMN_NAME F_FIELD,
data_type F_DATATYPE,
CHARACTER_MAXIMUM_LENGTH F_DATALENGTH,
NUMERIC_PRECISION F_PRECISION,
NUMERIC_SCALE F_DECIMAL_DIGITS,
IF
( IS_NULLABLE = 'YES', '1', '0' ) F_ALLOWNULL,
COLUMN_COMMENT F_FIELDNAME,
IF
( COLUMN_KEY = 'PRI', '1', '0' ) F_PRIMARYKEY,
column_default F_DEFAULTS,
CONCAT( upper( COLUMN_NAME ), '(', COLUMN_COMMENT, ')' ) AS 'F_DESCRIPTION'
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = '$tablename'
AND TABLE_SCHEMA = '$dbname'";
return Db::query($sql);
}
/**
* desc获取数据表中某个字段的数据类型含这个字段的其它属性
*
* 查询结果字段名说明
*
* F_FIELD字段名称
* F_DATATYPE数据类型
* F_DATALENGTH数据长度int类型默认为null,业务处理时默认为10即可
* F_PRECISION精度
* F_DECIMAL_DIGITS小数位数
* F_ALLOWNULL是否允许为null值(1是0否)
* F_FIELDNAME字段名称
* F_PRIMARYKEY是否主键(1是0否)
* F_DEFAULTS字段默认值
*
* authorwh
* @param string $dbname
* @param string $tablename
* @param string $fieldname
* @return mixed
*/
function getFieldDataType(string $dbname,string $tablename,string $fieldname){
$sql = "SELECT
COLUMN_NAME F_FIELD,
data_type F_DATATYPE,
CHARACTER_MAXIMUM_LENGTH F_DATALENGTH,
NUMERIC_PRECISION F_PRECISION,
NUMERIC_SCALE F_DECIMAL_DIGITS,
IF
( IS_NULLABLE = 'YES', '1', '0' ) F_ALLOWNULL,
COLUMN_COMMENT F_FIELDNAME,
IF
( COLUMN_KEY = 'PRI', '1', '0' ) F_PRIMARYKEY,
column_default F_DEFAULTS,
CONCAT( upper( COLUMN_NAME ), '(', COLUMN_COMMENT, ')' ) AS 'F_DESCRIPTION'
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = '$tablename'
AND TABLE_SCHEMA = '$dbname'
AND COLUMN_NAME='$fieldname'
";
$ar = Db::query($sql);
return $ar[0];
}
/**
* desc获取数据表中某个字段的数据类型
*
* 不含这个字段的其它属性
*
*
* 查询结果字段名说明
*
* F_FIELD字段名称
* F_DATATYPE数据类型
* F_DATALENGTH数据长度int类型默认为null,业务处理时默认为10即可
* F_PRECISION精度
* F_DECIMAL_DIGITS小数位数
* F_ALLOWNULL是否允许为null值(1是0否)
* F_FIELDNAME字段名称
* F_PRIMARYKEY是否主键(1是0否)
* F_DEFAULTS字段默认值
*
* authorwh
* @param string $dbname
* @param string $tablename
* @param string $fieldname
* @return mixed
*/
function getFieldDataTypeVal(string $dbname,string $tablename,string $fieldname){
$sql = "SELECT
COLUMN_NAME F_FIELD,
data_type F_DATATYPE,
CHARACTER_MAXIMUM_LENGTH F_DATALENGTH,
NUMERIC_PRECISION F_PRECISION,
NUMERIC_SCALE F_DECIMAL_DIGITS,
IF
( IS_NULLABLE = 'YES', '1', '0' ) F_ALLOWNULL,
COLUMN_COMMENT F_FIELDNAME,
IF
( COLUMN_KEY = 'PRI', '1', '0' ) F_PRIMARYKEY,
column_default F_DEFAULTS,
CONCAT( upper( COLUMN_NAME ), '(', COLUMN_COMMENT, ')' ) AS 'F_DESCRIPTION'
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = '$tablename'
AND TABLE_SCHEMA = '$dbname'
AND COLUMN_NAME='$fieldname'
";
$ar = Db::query($sql);
return $ar[0]['F_DATATYPE'];
}
/**
* desc获取数据表中某个字段的属性
*
* 查询结果字段名说明
*
* F_FIELD字段名称
* F_DATATYPE数据类型
* F_DATALENGTH数据长度int类型默认为null,业务处理时默认为10即可
* F_PRECISION精度
* F_DECIMAL_DIGITS小数位数
* F_ALLOWNULL是否允许为null值(1是0否)
* F_FIELDNAME字段名称
* F_PRIMARYKEY是否主键(1是0否)
* F_DEFAULTS字段默认值
*
* authorwh
* @param string $dbname
* @param string $tablename
* @param string $fieldname
* @return mixed
*/
function getFieldAttrVal(string $dbname,string $tablename,string $fieldname,string $attr){
$sql = "SELECT
COLUMN_NAME F_FIELD,
data_type F_DATATYPE,
CHARACTER_MAXIMUM_LENGTH F_DATALENGTH,
NUMERIC_PRECISION F_PRECISION,
NUMERIC_SCALE F_DECIMAL_DIGITS,
IF
( IS_NULLABLE = 'YES', '1', '0' ) F_ALLOWNULL,
COLUMN_COMMENT F_FIELDNAME,
IF
( COLUMN_KEY = 'PRI', '1', '0' ) F_PRIMARYKEY,
column_default F_DEFAULTS,
CONCAT( upper( COLUMN_NAME ), '(', COLUMN_COMMENT, ')' ) AS 'F_DESCRIPTION'
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = '$tablename'
AND TABLE_SCHEMA = '$dbname'
AND COLUMN_NAME='$fieldname'
";
$ar = Db::query($sql);
return $ar[0][$attr];
}
}

View File

@@ -0,0 +1,162 @@
<?php
/*
* description
* authorwh
* email
* createTime{2022/02/24} {10:48}
*/
namespace wanghua\general_utility_tools_php\db\mysql\lib;
use think\Db;
use wanghua\general_utility_tools_php\tool\Tools;
class Table
{
/**
* desc创建表初始化默认字段
* authorwh
* @param $data
* @return bool
*/
function createTable($data){
$tablename = $data['tablename'];
$title = $data['title'];
//创建表
$sql = "CREATE TABLE IF NOT EXISTS {$tablename}(
id int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
create_time timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY key(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='{$title}' ;";
Db::execute($sql);
}
/**
* desc改注释
* authorwh
* @param $tablename
* @param $comment
* @return bool
*/
function updateComment($tablename, $comment){
$sql = "ALTER TABLE {$tablename} COMMENT '{$comment}';";
Db::execute($sql);
}
/**
* desc改表名
* authorwh
* @param $tablename
* @param $new_table_name
* @return bool
*/
function updateTableName($tablename, $new_table_name){
$sql = "ALTER TABLE {$tablename} RENAME TO {$new_table_name}";
Db::execute($sql);
}
/**
* desc查询当前数据库所有的表名
* authorwh
* @return mixed
*/
function getTables(){
return array_column(Db::query('SHOW TABLES;'), 'Tables_in_'.config('database.database'));
}
/**
* desc获取数据表字所有段名
* authorwh
* @param $tablename
* @return array
*/
function getTableFields($tablename){
$dbname = config('database.database');
$sql = "SELECT COLUMN_NAME column_name,COLUMN_COMMENT column_comment,DATA_TYPE data_type
FROM information_schema.columns WHERE TABLE_NAME='{$tablename}' AND table_schema='{$dbname}'";
return array_column(Db::query($sql), 'column_name');
}
/**
* desc获取表的属性
* Name:
表名称
Engine:
表的存储引擎
Version:
版本
Row_format:
行格式。对于MyISAM引擎这可能是DynamicFixed或Compressed。动态行的行长度可变例如Varchar或Blob类型字段。固定行是指行长度不变例如Char和Integer类型字段
Rows:
表中的行数。对于MyISAM和其他存储引擎这个值是精确的对于innoDB存储引擎这个值通常是估算的
Avg_row_length:
平均每行包括的字节数
Data_length:
整个表的数据量(以字节为单位)
Max_data_length:
表可以容纳的最大数据量,该值和存储引擎相关
Index_length:
索引占用磁盘的空间大小(以字节为单位)
Data_free:
对于MyISAM引擎表示已经分配但目前没有使用的空间。这部分空间包含之前被删除的行以及后续可以被insert利用到的空间
Auto_increment:
下一个Auto_increment的值
Create_time:
表的创建时间
Update_time:
表的最近更新时间
Check_time:
使用 check table 或myisamchk工具最后一次检查表的时间
Collation:
表的默认字符集和字符排序规则
Checksum:
如果启用,保存的是整个表的实时校验和
Create_options:
创建表时指定的其他选项
Comment:
包含了其他额外信息对于MyISAM引擎保存的是表在创建时带的注释。如果表使用的是innodb引擎 保存的是InnoDB表空间的剩余空间。如果是一个视图注释里面包含了VIEW字样。
*
* @param $tablename 表名(无表名则查询所有表)
*
* authorwh
* @return mixed
*/
function getTableInfo(string $tablename=''){
if($tablename){
$sql = "SHOW TABLE STATUS WHERE Name = 'fa_agent';";
return Db::query($sql);
}
$sql = 'show table status;';
return Db::query($sql);
}
/**
* desc检查某个表是否存在
*
* authorwh
* @param string $dbname
* @param string $tablename
* @return bool
*/
function isExistTable(string $dbname,string $tablename){
$sql = "SELECT TABLE_SCHEMA,TABLE_NAME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA ='$dbname'
AND TABLE_NAME = '$tablename';";
return empty(Db::query($sql));
}
/**
* desc获取表注释
*
* authorwh
*/
function getTableComment($table){
$prefix = config('database.prefix');
$table = $prefix.$table;
$sql = 'show table status;';
$arr = Db::query($sql);
$tmp = Tools::key_val_arr($arr,'Name','Comment');
return empty($tmp[$table])?'':$tmp[$table];
}
}

View File

@@ -0,0 +1,15 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/4/5} {10:49}
*/
namespace wanghua\general_utility_tools_php\douyin;
class BaseDouYin
{
}

View File

@@ -0,0 +1,193 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/4/4} {12:36}
*/
namespace wanghua\general_utility_tools_php\douyin;
use wanghua\general_utility_tools_php\http\Curl;
use wanghua\general_utility_tools_php\tool\Tools;
class DouYinMiniGame extends BaseDouYin
{
public $appid = 'tt3c974a2aa3a6ee7c02';
public $secret = '5c2414e47a18b9655a164b14ef31ac7cb58179ca';
/**
* desc每2小时更新一次
*
* https://developer.open-douyin.com/docs/resource/zh-CN/mini-game/develop/server/interface-request-credential/get-access-token
*
* {"err_no":0,"err_tips":"success","data":{"access_token":"0801121846384c316e52536e5a76396769565145696f597742673d3d","expires_in":7200,"expiresAt":1712208063,"expires_at":1712208063}}
*
* authorwh
*/
function getAccessToken(){
$url = "https://minigame.zijieapi.com/mgplatform/api/apps/v2/token";
$method = 'POST';
//$Scope = 'open.ttgame.mgplatform';
$params = [
'appid'=>$this->appid,
'secret'=>$this->secret,
'grant_type'=>'client_credential',
];
$header = [
'Accept: application/json',
'content-type: application/json'
];
$res = Curl::curl_request($url,$method,json_encode($params),$header);
$json_arr = json_decode($res, true);
if($json_arr['err_no']){
return Tools::set_res($json_arr['err_no'],$json_arr['err_tips']);
}
//保存
session('ses_dou_yin_access_token',$json_arr['data']);
return Tools::set_ok('ok',$json_arr['data']);
}
/**
* desc实时获取token
* authorwh
* @return array
*/
function realTimeGetAccessToken(){
$json_arr = session('ses_dou_yin_access_token');
if(empty($json_arr)){
return $this->getAccessToken();
}
if(time() >= $json_arr['expiresAt']){
//过期
return $this->getAccessToken();
}
return Tools::set_ok('ok',$json_arr);
}
/**
* desc前端tt.login触发调用
*
* authorwh
*/
function jscode2session($code='',$anonymous_code=''){
$url = 'https://minigame.zijieapi.com/mgplatform/api/apps/jscode2session';
//$Scope = 'open.ttgame.mgplatform';
$method = 'GET';
if(empty($code) && empty($anonymous_code)){
return Tools::set_fail('tt.login 接口返回的匿名登录凭证code 和 anonymous_code 至少要有一个)');
}
$params = [
'appid'=>$this->appid,
'secret'=>$this->secret,
];
if($code){
$params['code'] = $code;
}
if($anonymous_code){
$params['anonymous_code'] = $anonymous_code;
}
$header = [
'Accept: application/json',
'content-type: application/json'
];
$url .= '?'.http_build_query($params);
//dump($url);
Tools::log_to_write_txt(['input'=>$url]);
$res = Curl::curl_request($url,$method,[],$header);
Tools::log_to_write_txt(['output'=>$res]);
//dump($res);
$json_arr = json_decode($res, true);
//dump($json_arr);die;
if($json_arr['error']){
return Tools::set_res($json_arr['errcode'],$json_arr['errmsg']);
}
//保存
session('ses_dou_yin_js_code_2_session',$json_arr);
return Tools::set_ok('ok',$json_arr);
}
/**
* desc创建二维码
* 接口说明
* 获取小程序/小游戏的二维码。该二维码可通过任意 app 扫码打开,
* 能跳转到开发者指定的对应字节系 app 内拉起小程序/小游戏,并传入开发者指定的参数。
* 通过该接口生成的二维码,永久有效,暂无数量限制。
*
* ⚠ Tip在使用该功能之前请记得先配置您的默认分享文案和图片配置方式可参考论坛。
* ⚠ Tip小程序的 path 要 encode 一次,如 pages%3fparam%3dtrue小游戏的 path 为 JSON 字符串,
* 如{"param":true},否则会导致取不到。
*
* 参数:
* code和anonymous_code二选一 必须
* appname 可选,目标打开应用名称 默认douyin
* background 可选背景色rgb格式英文逗号隔开默认透明色
* path 可选,小程序/小游戏启动参数,小程序则格式为 encode({path}?{query}),小游戏则格式为 JSON 字符串,默认为空
* width 宽度 可选,二维码宽度,单位 px最小 280px最大 1280px默认为 430px
* line_color 可选二维码线条颜色默认为黑色rgb格式英文逗号隔开默认黑色
* set_icon 可选,是否展示小程序/小游戏 icon默认不展示传yes展示no不展示默认no
*
* authorwh
*/
function createQRCode($path='',$appname='',$width='',$background='',$line_color='',$set_icon='no'){
try {
$url = 'https://minigame.zijieapi.com/mgplatform/api/apps/qrcode';
//$Scope = 'open.ttgame.mgplatform';
$method = 'POST';
$json_arr = $this->realTimeGetAccessToken();
if(empty($json_arr)){
return Tools::set_fail('请重新授权');
}
$params = [
'access_token'=>$json_arr['data']['access_token'],
];
if($appname){
$params['appname'] = $appname;
}
if($width){
$params['width'] = $width;
}
//if($path){
//小程序/小游戏启动参数,小程序则格式为 encode({path}?{query}),小游戏则格式为 JSON 字符串,默认为空
$params['path'] = $path;
//}
//英文逗号隔开r,g,b格式
if($background){
$background_rgb = explode(',',$background);
$params['r'] = $background_rgb[0];
$params['g'] = $background_rgb[1];
$params['b'] = $background_rgb[2];
}
if($line_color){
$line_color_rgb = explode(',',$line_color);
$params['r'] = $line_color_rgb[0];
$params['g'] = $line_color_rgb[1];
$params['b'] = $line_color_rgb[2];
}
$params['set_icon'] = $set_icon=='yes';
$header = [
'Accept: application/json',
'content-type: application/json'
];
Tools::log_to_write_txt(['input'=>input(),'$params'=>$params]);
$res = Curl::curl_request($url,$method,json_encode($params),$header);
Tools::log_to_write_txt(['output'=>$res]);
//流
return $res;
}catch (\Exception $e){
Tools::error_txt_log($e);
return Tools::set_fail();
}
}
}

View File

@@ -0,0 +1,88 @@
<?php
/*
* description
* authorwh
* email
* createTime{2022/02/10} {15:51}
*/
namespace wanghua\general_utility_tools_php\encrypt;
/**
* 3DES,三重数据加密算法
*
* Class TripleDES
* @package wanghua\general_utility_tools_php\encrypt
*/
class TripleDES
{
/**
* desc加密
*
* authorwh
* @param string $value
* @param $key
* @return string
*/
public function encrypt(string $value, $key)
{
$value = self::PaddingPKCS7($value);
//AES-128-ECB|不能用 AES-256-CBC|16 AES-128-CBC|16 BF-CBC|8 aes-128-gcm|需要加$tag DES-EDE3-CBC|8
$cipher = "DES-EDE3";
// if (in_array($cipher, openssl_get_cipher_methods())) {}
$result = openssl_encrypt($value, $cipher, $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, "");
return base64_encode($result);
}
/**
* @title 解密
*
* @param string $str 要传的参数
* @param $key
* @return bool|false|string
*
*/
public function decrypt(string $str, $key)
{
$decrypted = openssl_decrypt(base64_decode($str), 'DES-EDE3', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, "");
$ret = self::UnPaddingPKCS7($decrypted);
return $ret;
}
/**
* desc
* authorwh
* @param $data
* @return string
*/
private function PaddingPKCS7($data)
{
//$block_size = mcrypt_get_block_size('tripledes', 'cbc');//获取长度
//$block_size = openssl_cipher_iv_length('tripledes', 'cbc');//获取长度
$block_size = 8;
$padding_char = $block_size - (strlen($data) % $block_size);
$data .= str_repeat(chr($padding_char), $padding_char);
return $data;
}
/**
* desc
* authorwh
* @param $text
* @return false|string
*/
private function UnPaddingPKCS7($text)
{
$pad = ord($text{strlen($text) - 1});
if ($pad > strlen($text)) {
return false;
}
if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
return false;
}
return substr($text, 0, -1 * $pad);
}
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/7/1} {19:05}
*/
namespace wanghua\general_utility_tools_php\errorcode;
class AuthError
{
const PERMISSION_DENIED = [60000, '权限拒绝'];
const ACCESS_REFUSE_ERROR = [60100, '访问被拒绝'];//请求不合法资源
const ACCESS_TIMES_ERROR = [60200, '访问次数限制'];
}

View File

@@ -0,0 +1,84 @@
<?php
/*
* description
* authorwh
* email
* createTime{2019/11/12} {21:01}
*/
namespace wanghua\general_utility_tools_php\errorcode;
/**
* 系统级错误, 业务错误代码放在业务层,不要放在此处
* Class SystemError
* @package errorcode
*/
class Code
{
//region 系统级别错误
const SUCCESS = [200, 'SUCCESS'];
const REQUEST_ERROR = [10000, '请求错误'];//一般url资源错误url错误、敏感参数错误等
const REQUEST_TYPE_ERROR = [10001, '请求类型错误'];//ajax get post
const FAILED = [10002, '操作失败'];
const SOURCE_NOT_FIND = [10003, '资源找不到'];
const SYSTEM_ERROR = [500, '系统错误'];
const PERMISSION_DENIED = [10005, '权限拒绝'];
const ACCESS_REFUSE_ERROR = [10006, '访问被拒绝'];//请求不合法资源
const ACCESS_TIMES_ERROR = [10007, '访问次数限制'];
//endregion 系统级别错误
//region 短信错误
const MESSAGE_SEND_ERROR = [13500, '短信发送失败'];
const MESSAGE_SEND_ULTRALIMIT = [13600, '短信发送超限'];
const MESSAGE_VERIFY_FAILED = [13700, '短信验证失败'];
const MESSAGE_NUM_LITTLE = [13800, '短信余额不足'];
const MESSAGE_SEND_EXCEPTION = [13900, '短信发送异常'];
//endregion
//region 文件上传
const UPLOAD_FAILED = [14000, '上传失败'];
const UPLOAD_FILE_IS_EMPTY = [14000, '无文件上传'];
const UPLOAD_FILE_SIZE_EMPTY = [14100, '文件内容为空'];
const UPLOAD_FILE_SIZE_ERROR = [14200, '文件大小超过限制'];
const UPLOAD_FILE_MIME_ERROR = [14300, '文件类型错误'];
const UPLOAD_FILE_EXT_ERROR = [14500, '文件扩展名错误'];
const FILE_FROM_ERROR = [14520, '来源错误'];
//endregion
//region 数据库错误
const DATA_IS_EMPTY = [50000, '未获取到相关数据'];
const UPDATE_ERROR = [50001, '修改异常'];
const INSERT_ERROR = [50002, '数据保存异常'];
const SELECT_ERROR = [50003, '查询异常'];
const DATA_REPEAT_ERROR = [50005, '数据重复'];
const DATA_INSERT_ERROR = [50006, '数据入库异常'];
//endregion
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/7/1} {18:49}
*/
namespace wanghua\general_utility_tools_php\errorcode;
class DbError
{
//region 数据库错误
const DATA_IS_EMPTY = [16000, '未获取到相关数据'];
const UPDATE_ERROR = [16100, '修改异常'];
const INSERT_ERROR = [16200, '数据保存异常'];
const SELECT_ERROR = [16300, '查询异常'];
const DATA_REPEAT_ERROR = [16400, '数据重复'];
const DATA_INSERT_ERROR = [16500, '数据入库异常'];
//endregion
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/7/1} {18:47}
*/
namespace wanghua\general_utility_tools_php\errorcode;
class EmailError
{
//region 邮件错误
const EMAIL_RECEIVER_IS_EMPTY = [23100, '未设置收信人'];
const EMAIL_SEND_FAILED = [23200, '邮件发送失败'];
//endregion
}

View File

@@ -0,0 +1,29 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/7/1} {18:48}
*/
namespace wanghua\general_utility_tools_php\errorcode;
class FileUploadError
{
//region 文件上传
const UPLOAD_FAILED = [24000, '上传失败'];
const UPLOAD_FILE_IS_EMPTY = [24100, '无文件上传'];
const UPLOAD_FILE_SIZE_EMPTY = [24200, '文件内容为空'];
const UPLOAD_FILE_SIZE_ERROR = [24300, '文件大小超过限制'];
const UPLOAD_FILE_MIME_ERROR = [24400, '文件类型错误'];
const UPLOAD_FILE_EXT_ERROR = [24500, '文件扩展名错误'];
const FILE_SOURCE_ERROR = [24600, '文件来源错误'];
//endregion
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/7/1} {18:59}
*/
namespace wanghua\general_utility_tools_php\errorcode;
class LoginError
{
const OFF_LINE_ERROR = [30100, '未登录'];
const LOGIN_SESSION_INVALID = [30200, '登录已过期,请重新登录'];
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/7/3} {14:14}
*/
namespace wanghua\general_utility_tools_php\errorcode;
class RequestError
{
//region 请求错误
const SUCCESS = [200, 'SUCCESS'];
const REQUEST_TYPE_ERROR = [50601, '请求类型错误'];//ajax get post
const SOURCE_NOT_FIND = [50400, '资源找不到'];
const SYSTEM_ERROR = [50500, '系统错误'];
const REQUEST_ERROR = [50600, '请求错误'];//一般url资源错误url错误、敏感参数错误等
const FAILED = [50602, '请求失败'];
//endregion 请求错误
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/7/1} {18:49}
*/
namespace wanghua\general_utility_tools_php\errorcode;
class SmsError
{
//region 短信错误
const MESSAGE_SEND_ERROR = [43500, '短信发送失败'];
const MESSAGE_SEND_ULTRALIMIT = [43600, '短信发送超限'];
const MESSAGE_VERIFY_FAILED = [43700, '短信验证失败'];
const MESSAGE_NUM_LITTLE = [43800, '短信余额不足'];
const MESSAGE_SEND_EXCEPTION = [43900, '短信发送异常'];
//endregion
}

View File

@@ -0,0 +1,15 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/7/1} {18:50}
*/
namespace wanghua\general_utility_tools_php\errorcode;
class SystemError
{
}

View File

@@ -0,0 +1,264 @@
<?php
/*
* description
* authorwh
* email
* createTime{2021/4/26} {9:45}
*/
namespace wanghua\general_utility_tools_php\es;
use wanghua\general_utility_tools_php\Date;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* @deprecated 废弃,即将删除
*
* 【es原生操作库】
* 【仅支持框架ThinkPHP5+、 PHP7.2+】
*
* BaseElasticsearch.php类库的替代库
*
* 使用步骤:
* 初始化设置ip、索引前缀-普通索引请设置为空字符串)
* 设置索引范围例如查询本月如果索引不是按日期规律设置请使用普通索引设置方法setIndexNormal并设置前缀为空字符串
* 设置查询方法eg: _search
* 设置查询参数(查询条件)
* 执行
* 返回结果
* Class ElasticsearchUtility
* @package app\admin\logic
*/
class Elasticsearch
{
protected $es_log_file = 'es_log';
//最终拼装的请求地址
protected $post_url = ''; //eg: $url = 'http://49.4.3.4:9111/qa-item-2021.04/_search';
//查询参数
protected $query_param = [];
//查询方法
protected $query_method = '_search';
//文档索引
protected $index = '';
//服务器ip
protected $ip = '';
//索引前缀(优先设置)
protected $index_sign = '';//eg: qa-stat-
//设置查询日期格式符号
protected $doc_date_sign = '.';
//设置是哪个方法调用本es库用于日志记录
protected $exe_func = '';
/**
* ElasticsearchUtility constructor.
* @param string $ip_port 协议+服务器ip+端口号,必须
* @param string $index_sign 当es索引为有规律的日期索引时可使用前缀并配合日期索引设置方法体验更佳
*/
public function __construct(string $ip_port, string $index_sign='')
{
$this->ip = false===strpos($ip_port,'http')?'http://'.$ip_port:$ip_port;
$this->index_sign = $index_sign;
}
/**
* desc设置查询方法默认_search
* authorwh
* @param $method_name
*/
function setQueryMethod($method_name){
$this->query_method = $method_name;
}
/**
* desc根据日期索引查询文档
* 注:默认一个月
*
* eg:'qa-stat-2021.01';
*
* 注如果es设置的索引没有规律也不是日期作为索引请设置普通索引setIndexNormal且请在初始化时设置索引前缀index_sign为""字符串
* authorwh
*/
function setIndexDefault(){
$this->setRequestIndex(date('Y'.$this->doc_date_sign.'m'));
}
/**
* desc设置要查询的日期文档索引
*
* 1、按年检索文档[建议5年左右]
*
* eg: qa-stat-2018.*,qa-stat-2019.*,qa-stat-2020.*,qa-stat-2021.*
*
* 注意当索引过长es会抛出索引太长异常
*
* 注如果es设置的索引没有规律也不是日期作为索引请设置普通索引setIndexNormal且请在初始化时设置索引前缀index_sign为""字符串
*
* authorwh
* @param string $start_time 开始时间 eg2010-01-01 08:05:55
* @param string $end_time 结束时间 eg2021-12-31 12:05:00
* @return string
*/
function setIndexYearDate(string $start_time, string $end_time){
//默认查询索引
$index = date('Y'.$this->doc_date_sign.'*', strtotime($start_time));
$sign = ',';//分隔符
$m = date('Y', strtotime($end_time)) - date('Y', strtotime($start_time));
//拼装查询索引
for ($i=0; $i<$m; $i++){
$index .= $sign.($this->index_sign.(date('Y', strtotime($start_time))+$i).$this->doc_date_sign.'*');
}
$this->setRequestIndex($index);
}
/**
* desc设置日期索引
*
* 检索全部日期索引文档
*
* 例如es设置的索引为
* qa-stat-2021.01
qa-stat-2021.02
qa-stat-2021.03
qa-stat-2021.04
*
* 实际查询自动设置为qa-stat-*
*
* 注如果es设置的索引没有规律也不是日期作为索引请设置普通索引setIndexNormal且请在初始化时设置索引前缀index_sign为""字符串
*
* authorwh
* @return string
*/
function setIndexAllDate(){
$this->setRequestIndex('*');
}
/**
* desc设置普通索引
*
* 调用此方法请在初始化时设置索引前缀index_sign为""空字符串
*
* authorwh
* @param string $index
*/
function setIndexNormal(string $index){
$this->setRequestIndex($index);
}
/**
* desc设置跨月份索引
*
* eg: qa-stat-2021.01,qa-stat-2021.02
*
* 注如果es设置的索引没有规律也不是日期作为索引请设置普通索引setIndexNormal且请在初始化时设置索引前缀index_sign为""字符串
*
* authorwh
* @param string $start_time eg:'2020-11'
* @param string $end_time eg:'2021-01'
* @return string eg: qa-stat-2020.11,qa-stat-2020.12,qa-stat-2021.01
*/
function setIndexDate(string $start_time, string $end_time){
$date = new Date();
//计算月份
$m = $date->dateCutMonth($start_time, $end_time);
////解决跨年且不足1个月时索引设置错误问题
//if($m == 0 && (date('Y', strtotime($end_time)) > date('Y', strtotime($start_time)))){
// $m = 1;
//}
//格式
$date->date_format = 'Y-m';
//默认查询索引
$index = date('Y'.$this->doc_date_sign.'m', strtotime($start_time));
$tmp_time = $start_time;
$sign = ',';//分隔符
//拼装查询索引
for ($i=0; $i<$m; $i++){
$tmp_time = $date->addTime(1, 'M', strtotime($tmp_time));
$index .= $sign.$this->index_sign.(date('Y'.$this->doc_date_sign.'m', strtotime($tmp_time)));
}
$this->setRequestIndex($index);
}
/**
* desc设置查询参数
* authorwh
* @param string $query_param_json
*/
function setQueryParam(string $query_param_json){
$this->query_param = $query_param_json;
$this->post_url = $this->ip.'/'.$this->index.'/'.$this->query_method;
}
/**
* desc获取查询参数用于调试
* authorwh
* @return array
*/
function getQueryParam(){
return [
'ip'=>$this->ip,
'index_sign'=>$this->index_sign,
'index'=>$this->index,
'query_method'=>$this->query_method,
'post_url'=>$this->post_url,
'query_param'=>$this->query_param,
];
}
/**
* desc执行es查询
* authorwh
* @return array|bool|int|string
* @throws \Exception
*/
function execute(){
if(empty($this->ip)) throw new \Exception('请设置ip');
//if(empty($this->index_sign)) throw new \Exception('请设置索引前缀');
if(empty($this->index)) throw new \Exception('请设置索引');
if(empty($this->query_method)) throw new \Exception('请设置查询方法');
if(empty($this->query_param)) throw new \Exception('请设置查询参数');
Tools::log_to_write_txt(['exe_func'=>$this->exe_func,'request_url'=>$this->post_url,'IN'=>" | IN: ",'query_params'=>$this->query_param, 'input'=>input()], $this->es_log_file);
$result = Tools::curl_post($this->post_url, $this->query_param);
Tools::log_to_write_txt(['exe_func'=>$this->exe_func,'OUT'=>" | OUT: ", 'result'=>$result], $this->es_log_file);
return $result;
}
/**
* desc设置是哪个方法调用本es库用于日志记录
* authorwh
* @param string $function
*/
function setExecuteFunction(string $function){
$this->exe_func = $function;
}
/**
* desc设置请求文档索引
* authorwh
* @param string $index
*/
protected function setRequestIndex(string $index){
$this->index = $this->index_sign.$index;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/11/14} {10:01}
*/
namespace wanghua\general_utility_tools_php\exception;
use think\Exception;
class BaseException extends Exception
{
}

View File

@@ -0,0 +1,27 @@
# 系统业务级异常类
1. api异常
2. 请求异常
3. api异常
4. 响应异常
5. 待扩展......
##使用说明
例1
if(empty($params)){
//参数错误,抛出空参数异常
throw new RequestException(RequestException::EMPTY_PARAM_ERROR);
}
例2
//假设返回结果格式为:['code'=>200, 'msg'=>'操作成功', 'data'=>[]]
if(is_null($result['code'])){
//无code字段抛出响应格式错误
throw new ResponseException(ResponseException::RESPONSE_FORMAT_ERROR);
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* description
* authorwh
* email
* createTime{2022/03/08} {14:17}
*/
namespace wanghua\general_utility_tools_php\exception;
use think\exception\Handle;
use think\Request;
use wanghua\general_utility_tools_php\SundryConfig;
use wanghua\general_utility_tools_php\tool\EmailTool;
use wanghua\general_utility_tools_php\tool\Tools;
class SystemException extends Handle
{
public function render(\Exception $e)
{
// 参数验证错误
//if ($e instanceof ValidateException) {
// return json($e->getError(), 422);
//}
// 请求异常
//if ($e instanceof HttpException && request()->isAjax()) {
// return response($e->getMessage(), $e->getStatusCode());
//}
Tools::log_to_write_txt([
'error'=>'系统错误.'.$e->getMessage(),
'input'=>Request::instance()->input(),
'error_info'=>$e->getTraceAsString()
]);
$title = '新的【系统异常】,请立即处理';
//邮件通知管理员
$mail_content = '<p>error'.$e->getMessage().'</p>';
$mail_content .= '<p>url'.request()->url(true).'</p>';
$mail_content .= '<p>ip'.request()->ip().'</p>';
$mail_content .= '<p>详情请登录宝塔查看日志</p>';
$emails = SundryConfig::val('admin_error_log_email');
if(config('sys_env') == 'PROD'){//线上环境才发错误邮件
EmailTool::email_to_person($title,$mail_content,$emails);
}
if(request()->LogObj){
request()->LogObj->flush();
}
if(request()->ServeLogObj){
request()->ServeLogObj->flush();
}
//可以在此交由系统处理
return parent::render($e);
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/11/14} {10:04}
*/
namespace wanghua\general_utility_tools_php\exception\api;
use wanghua\general_utility_tools_php\exception\BaseException;
class ApiException extends BaseException
{
const API_RESPONSE_FORMAT_ERROR='The api response result is incorrectly formatted: code is not exist.';
const EMPTY_PARAM_ERROR ='param is empty.';
const EMPTY_URL_ERROR ='url of param is empty.';
}

View File

@@ -0,0 +1,18 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/11/14} {10:13}
*/
namespace wanghua\general_utility_tools_php\exception\request;
use wanghua\general_utility_tools_php\exception\BaseException;
class RequestException extends BaseException
{
const EMPTY_PARAM_ERROR ='param is empty.';
const EMPTY_URL_ERROR ='url of param is empty.';
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/11/14} {10:48}
*/
namespace wanghua\general_utility_tools_php\exception\response;
use wanghua\general_utility_tools_php\exception\BaseException;
class ResponseException extends BaseException
{
const RESPONSE_FORMAT_ERROR='The response result is incorrectly formatted.';
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/1/16} {17:15}
*/
namespace wanghua\general_utility_tools_php\fastadmin;
use app\common\model\TabConf;
use think\Db;
/**
* 为fastadmin框架定制的功能
*
* Class FastadminTools
* @package wanghua\general_utility_tools_php\fastadmin
*/
class FastadminTools
{
/**
* desc线上需要自动隐藏的菜单
*
* 当你有这种需求:
* 我的菜单只需要本地显示,线上或非本地环境不需要显示时,就可以使用此方法。
* 为什么需要此方法?
* 因为,我每次在本地增加功能并新增菜单之后,都需要把本地的菜单同步到线上,但是命令管理菜单,或者有些只需要开发者才能
* 动的菜单,需要在线上隐藏。我又不想每次同步之后去改菜单表。
* 手动去菜单表改也是可以,但是次数多了就很麻烦,有时候会忘记。
*
* 如:命令管理菜单、开发者相关配置
*
* authorwh
* @param array $menu_arr 需要隐藏的菜单数组
* @param string $sys_env
* @return bool
*/
static function auto_hidden_menu_online(array $menu_arr, string $sys_env){
foreach ($menu_arr as $menu_name){
if($sys_env == 'LOCAL'){
//本地显示
Db::table('fa_auth_rule')->where('name',$menu_name)->data(['status'=>'normal'])->update();
}else{
Db::table('fa_auth_rule')->where('name',$menu_name)->data(['status'=>'hidden'])->update();
}
}
return true;
}
}

View File

@@ -0,0 +1,236 @@
<?php
/*
* description
* authorwh
* email
* createTime{2024/2/4} {16:59}
*/
namespace wanghua\general_utility_tools_php\file;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* 文件、文件夹操作类
*
* eg读取文件夹、复制文件等
*
* Class FileOperate
* @package wanghua\general_utility_tools_php\file
*/
class File
{
public $file_ext = '*.*';//扩展名
public $unzip_path = '';//解压路径
/**
* desc读取文件和文件夹
*
* authorwh
* @param $folderPath 读取目标路径下的文件夹和文件
*
* @param $dirs 存储文件路径 eg:D:\wanghua\old_files
*
//array(3) {
// [0] => array(1) {
// ["path"] => string(25) "D:\wanghua\test_files/111"
// }
// [1] => array(1) {
// ["path"] => string(25) "D:\wanghua\test_files/222"
// }
//}
*
*
*
*
* @param $file_name_arr 存储文件路径、文件名称
*
//array(64) {
// [0] => array(3) {
// ["path"] => string(25) "D:\wanghua\test_files/111"
// ["path_file"] => string(33) "D:\wanghua\test_files/111/111.txt"
// ["filename"] => string(7) "111.txt"
// }
// [1] => array(3) {
// ["path"] => string(25) "D:\wanghua\test_files/111"
// ["path_file"] => string(37) "D:\wanghua\test_files/111/1111111.txt"
// ["filename"] => string(11) "1111111.txt"
// }
//}
*/
function read_folder($folderPath,&$dirs,&$file_name_arr) {
// 获取指定目录下所有文件和文件夹
$files = scandir($folderPath);
foreach ($files as $file) {
// 如果当前项为文件夹且不是"."或者".."
if (is_dir("$folderPath/$file") && !in_array($file, array('.', '..'))) {
$dirs[] = ['path'=>$folderPath.'/'.$file];//此时的$file是文件夹名称
// 调用自身进行递归操作
$this->read_folder("$folderPath/$file",$dirs,$file_name_arr);
} elseif (!in_array($file, array('.', '..'))){ // 如果当前项为文件而非"."或者".."
$file_name_arr[] = ['path'=>$folderPath,'path_file'=>"$folderPath/$file",'filename'=>$file];
}
}
}
/**
* desc复制文件
*
* authorwh
* @param array $sourceFileArr 源文件路径数组 path,path_file,filename
*
* @param string $destination_path 目标存放路径 D:\wanghua\test_files\new_files
*
* @param array $cp_type_arr 允许复制的文件后缀 eg:['jpg','png,'spine']
*
* @param bool $is_cover 是否覆盖 默认false不覆盖会以源文件名+微秒时间戳格式作为新的文件名
*
* @param bool $is_continue 相同文件是否跳过复制 默认 true
*
* 实战案例:
*
function testcopy(){
$source_dir = 'D:\wanghua\test_files';//源文件路径
$destination_path = 'D:\wanghua\test_files\new_files';//目标文件路径
$dirs = [];//存储文件路径
$file_name_arr = [];//含文件路径、名称
$file_obj = new File();
$file_obj->read_folder($source_dir,$dirs,$file_name_arr);
//dump($dirs);
//dump($file_name_arr);die;
$file_obj->copy($file_name_arr,$destination_path,['jpg','png,'spine']);
}
*
*/
function copy(array $sourceFileArr, string $destination_path, $cp_type_arr=['jpg','png'], $is_cover=false, $is_continue=false){
//设置不超时
set_time_limit(0);
//源文件路径 => 目标文件路径
foreach ($sourceFileArr as $key=>$file_arr){
if(!file_exists($file_arr['path_file'])){
continue;
}
//验证目标文件夹路径
if(!file_exists($destination_path)){
mkdir($destination_path,0777,true);
}
$name_arr = explode('.',$file_arr['filename']);
//验证符合条件的文件类型
if(!in_array($name_arr[1],$cp_type_arr)){
continue;
}
if(file_exists($destination_path.'/'.$file_arr['filename'])){
if($is_continue){
continue;//相同文件跳过复制
}
//覆盖
if($is_cover){
copy($file_arr['path_file'], $destination_path.'/'.$file_arr['filename']);
}else{
//不覆盖,名称区分
$microsecond = Tools::getMicrosecond();
copy($file_arr['path_file'], $destination_path.'/'.$name_arr[0].'('.$microsecond.').'.$name_arr[1]);
}
}else{
copy($file_arr['path_file'], $destination_path.'/'.$file_arr['filename']);
}
}
}
/**
* desc递归删除目录
*
* $dir 物理路径
*
* authorwh
*/
function recursive_delete($dir) {
if (!is_dir($dir)) {
return unlink($dir);
}
$handle = opendir($dir);
while (($file = readdir($handle)) !== false) {
if ($file === '.' || $file === '..') {
continue;
}
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
$this->recursive_delete($path);
} else {
unlink($path);
}
}
closedir($handle);
rmdir($dir);
}
/**
* desc解压ZIP文件并存储在本地
* authorwh
* @param resource $file_stream 文件流
* 例如:$response = $client->request('POST', $url, [
'headers' => $headers,
'body' => $body,
]);
// 处理响应
//$file_stream = $response->getBody();
* @param string $save_path 存储路径
* @return array
* @throws \Exception
*/
function unzip($file_stream,$save_path){
$zpi_file_name = 'zip_tmp_file_'.Tools::rand_str(20).'.zip';
//这个目录将会在结束时删除
$zipTempPath = $save_path.'/tmp_zip_files/'; // 临时存放ZIP文件的路径
//压缩文件保存目录
if(!file_exists($zipTempPath)){
mkdir($zipTempPath,0777,true);
}
$zpi_file = $zipTempPath.$zpi_file_name;
// 保存ZIP文件到临时路径
file_put_contents($zpi_file, $file_stream);
$this->unzip_path = $zipTempPath.'unzip_files/';
//解压目录
if(!file_exists( $this->unzip_path)){
mkdir( $this->unzip_path,0777,true);
}
// 解压ZIP文件
$zip = new \ZipArchive();
if ($zip->open($zpi_file) === TRUE) {
$zip->extractTo( $this->unzip_path);//移动到临时解压目录
$zip->close();
//遍历所有文件(file_ext后缀自行指定),遍历部分文件,如:*.json,*.zip,*.mp4,*.mp3等
$wavFiles = $this->glob_by_ext( $this->unzip_path.$this->file_ext);
//$urls = [];
//// 遍历解压后的文件生成URL
//foreach ($wavFiles as $wavFilePath) {
// // 生成外部访问的URL
// $downloadUrl = request()->domain() . explode('public',$wavFilePath)[1];
// $urls[] = $downloadUrl;
//}
//// 返回所有WAV文件的URL
//return ['wav_urls' => $urls];
return $wavFiles;
} else {
throw new \Exception('Failed to open ZIP file.');
}
}
/**
* desc获取指定扩展类型的文件列表
*
* authorwh
* @param string $file_ext 如: wav,pm4,mp3,json,zip
* @return array|false
*/
function glob_by_ext($file_ext='*'){
return glob( $this->unzip_path.'*.'.$file_ext);
}
}

View File

@@ -0,0 +1,5 @@
## 文件处理
#### 文件夹读取,文件复制
#### 文件上传

View File

@@ -0,0 +1,764 @@
<?php
/*
* description
* authorwh
* email
* createTime{2020/4/3} {9:23}
*/
namespace wanghua\general_utility_tools_php\file\upload;
use wanghua\general_utility_tools_php\oss\alicloud\Objects;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\MultipartStream;
use GuzzleHttp\Psr7\Utils;
use wanghua\general_utility_tools_php\huawei\obs\InitObs;
use wanghua\general_utility_tools_php\Mmodel;
use wanghua\general_utility_tools_php\oss\huawei\obs\Config;
use wanghua\general_utility_tools_php\tool\Tools;
/**
* 上传组件
* 支持单图,多图,单文件,多文件
* Class FileUpload
* @package libraries
*/
class FileUpload
{
//文件扩展
protected $file_ext = 'ico,jpg,jpeg,png,gif,pdf,zip,rar,xls,xlsx,doc,docx';
//图片扩展
protected $img_ext = 'ico,jpg,jpeg,png,gif';
public $unique_key = '';//唯一key临时文件会存储至该目录推荐使用用户id
public $file_lists_urls = [];//要读取的文件地址列表
/**
* 文件存放目录
*
* 注意:后面不要“/”
* 注意:后面不要“/”
* 注意:后面不要“/”
* @var string
*/
private $file_dir = '/uploads';
/**
* desc默认目录/uploads
*
* eg/uploads/user/head_image 或 /files_manage/goods/images
*
* 注意:后面不要“/”
* 注意:后面不要“/”
* 注意:后面不要“/”
*
* authorwh
* @param $file_dir
*/
function setFileDir($file_dir){
$this->file_dir = $file_dir;
}
/**
* desc单图
*
* 【必须确认php.ini中upload_max_filesize的配置足够大否则上传失败但不会报错
*
* authorwh
* @param string $file_upload_name 表单文件元素name值
* @param int $size 大小,默认2M
* @param string $ext 允许的扩展
* @return array
*/
function image($file_upload_name = 'file_upload', $size=10, $ext=''){
if(!$this->uploadMaxFilesizeCheck($size)){
return Tools::set_res(1, '上传文件受限');
}
// 获取表单上传文件 例如上传了001.jpg
$file = request()->file($file_upload_name);
if(empty($file)){
return Tools::set_fail('请上传文件');
}
// 移动到框架应用根目录/uploads/ 目录下
$outer_req_url = $this->file_dir.'/image';
$physics_path = 'public'.$outer_req_url;
$root_path = explode('vendor',__DIR__)[0];
$upload_dir = $root_path.$physics_path;
if(!is_dir($upload_dir)){
mkdir($upload_dir, 0777, true);
}
if(empty($file)){
return Tools::set_res(1, '上传文件不存在');
}
if(empty($ext)) $ext = $this->img_ext;
$info = $file->validate(['size'=>$size * 1024 * 1024, 'ext'=>$ext])->move($upload_dir);
if($info){
//原文件名
$source_file_name = $info->getInfo('name');
//文件类型
$file_type = $info->getInfo('type');
// 输出扩展名 jpg
$extension = $info->getExtension();
// 输出 20160820/42a79759f284b767dfcb2a0197904287.jpg
$save_name = $info->getSaveName();
// 输出 42a79759f284b767dfcb2a0197904287.jpg
$new_filename = $info->getFilename();
$relative_url = $outer_req_url.'/'.str_replace('\\', '/', $save_name);
$result = [
'prefix'=>request()->domain().$outer_req_url,//访问前缀
'source_file_name'=>$source_file_name,
'file_type'=>$file_type,
'extension'=>$extension,
'save_name'=>str_replace('\\', '/', $save_name),
'new_filename'=>$new_filename,
'real_path'=>$this->dealPath($root_path.'public'.$relative_url),//物理路径
'outer_req_url'=>request()->domain().$relative_url,
'outer_req_relative_url'=>$relative_url,
'size'=>$info->getSize(),
];
return Tools::set_res(0, '上传成功', $result);
}else{
// 上传失败获取错误信息
return Tools::set_res(1, $file->getError());
}
}
/**
* desc多图
*
* 【必须确认php.ini中upload_max_filesize的配置足够大否则上传失败但不会报错
*
* authorwh
* @param string $file_upload_name 表单文件元素name值
* @param int $size 大小,默认2M
* @param string $ext 允许的扩展
* @return array
*/
function images($file_upload_name = 'file_upload', $size=10, $ext=''){
if(!$this->uploadMaxFilesizeCheck($size)){
return Tools::set_res(1, '上传文件受限');
}
// 获取表单上传文件
$files = request()->file($file_upload_name);
if(empty($files)){
return Tools::set_fail('请上传文件');
}
$outer_req_url = $this->file_dir.'/image';
$physics_path = 'public'.$outer_req_url;
$root_path = explode('vendor',__DIR__)[0];
$upload_dir = $root_path.$physics_path;
if(!is_dir($upload_dir)){
mkdir($upload_dir, 0777, true);
}
if(empty($ext)) $ext = $this->img_ext;
$result = [];
$error = [];
foreach($files as $file){
// 移动到框架应用根目录/uploads/ 目录下
$info = $file->validate(['size'=>$size * 1024 * 1024, 'ext'=>$ext])->move($upload_dir);
if($info){
//原文件名
$source_file_name = $info->getInfo('name');
//文件类型
$file_type = $info->getInfo('type');
// 输出扩展名 jpg
$extension = $info->getExtension();
// 输出 20160820/42a79759f284b767dfcb2a0197904287.jpg
$save_name = $info->getSaveName();
// 输出 42a79759f284b767dfcb2a0197904287.jpg
$new_filename = $info->getFilename();
$relative_url = $outer_req_url.'/'.str_replace('\\', '/', $save_name);
$result[] = [
'prefix'=>request()->domain().$outer_req_url,//访问前缀
'source_file_name'=>$source_file_name,
'file_type'=>$file_type,
'extension'=>$extension,
'save_name'=>str_replace('\\', '/', $save_name),
'new_filename'=>$new_filename,
'real_path'=>$this->dealPath($root_path.'public'.$relative_url),//物理路径
'outer_req_url'=>request()->domain().$relative_url,
'outer_req_relative_url'=>$relative_url,
'size'=>$info->getSize(),
];
}else{
//忽略错误
// 上传失败获取错误信息
$error[] = $file->getError();
}
}
$msg = '上传成功';
$code = 0;
if(count($error) > 0){
$msg = '上传失败;提示:'.implode(',', $error);//顺带返回错误信息
$code = 1;
}
return Tools::set_res($code, $msg, $result);
}
/**
* desc单文件
*
* 【必须确认php.ini中upload_max_filesize的配置足够大否则上传失败但不会报错
*
* authorwh
* @param string $file_upload_name 表单文件元素name值
* @param int $size 大小,默认10M
* @param string $ext 允许的扩展
* @return array
*/
function file($file_upload_name = 'file_upload', $size=10, $ext=''){
if(!$this->uploadMaxFilesizeCheck($size)){
return Tools::set_res(1, '上传文件受限');
}
// 获取表单上传文件 例如上传了001.jpg
$file = request()->file($file_upload_name);
if(empty($file)){
return Tools::set_fail('请上传文件');
}
// 移动到框架应用根目录/uploads/ 目录下
$outer_req_url = $this->file_dir.'/file';
$physics_path = 'public'.$outer_req_url;
$root_path = explode('vendor',__DIR__)[0];
$upload_dir = $root_path.$physics_path;
if(!is_dir($upload_dir)){
mkdir($upload_dir, 0777, true);
}
if(empty($file)){
return Tools::set_res(1, '上传文件不存在');
}
if(empty($ext)) $ext = $this->file_ext;
$info = $file->validate(['size'=>$size * 1024 * 1024, 'ext'=>$ext])->move($upload_dir);
if($info){
//原文件名
$source_file_name = $info->getInfo('name');
//文件类型
$file_type = $info->getInfo('type');
// 输出扩展名 jpg
$extension = $info->getExtension();
// 输出 20160820/42a79759f284b767dfcb2a0197904287.jpg
$save_name = $info->getSaveName();
// 输出 42a79759f284b767dfcb2a0197904287.jpg
$new_filename = $info->getFilename();
$relative_url = $outer_req_url.'/'.str_replace('\\', '/', $save_name);
$result = [
'prefix'=>request()->domain().$outer_req_url,//访问前缀
'source_file_name'=>$source_file_name,
'file_type'=>$file_type,
'extension'=>$extension,
'save_name'=>str_replace('\\', '/', $save_name),
'new_filename'=>$new_filename,
'real_path'=>$this->dealPath($root_path.'public'.$relative_url),//物理路径
'outer_req_url'=>request()->domain().$relative_url,
'outer_req_relative_url'=>$relative_url,
'size'=>$info->getSize(),
];
return Tools::set_res(0, '上传成功', $result);
}else{
// 上传失败获取错误信息
return Tools::set_res(1, $file->getError());
}
}
/**
* desc多文件
*
* 【必须确认php.ini中upload_max_filesize的配置足够大否则上传失败但不会报错
*
* authorwh
* @param string $file_upload_name 表单文件元素name值
* @param int $size 单文件大小,默认10M
* @param string $ext 允许的扩展
* @return array
*/
function files($file_upload_name = 'file_upload', $size=10, $ext=''){
if(!$this->uploadMaxFilesizeCheck($size)){
return Tools::set_res(1, '上传文件受限');
}
$root_path = explode('vendor',__DIR__)[0];
// 获取表单上传文件
$files = request()->file($file_upload_name);
if(empty($files)){
return Tools::set_fail('请上传文件');
}
$outer_req_url = $this->file_dir.'/file';
$physics_path = 'public'.$outer_req_url;
$upload_dir = $root_path.$physics_path;
if(!is_dir($upload_dir)){
mkdir($upload_dir, 0777, true);
}
if(empty($ext)) $ext = $this->file_ext;
$result = [];
$error = [];
foreach($files as $file){
// 移动到框架应用根目录/uploads/ 目录下
$info = $file->validate(['size'=>$size * 1024 * 1024, 'ext'=>$ext])->move($upload_dir);
if($info){
//原文件名
$source_file_name = $info->getInfo('name');
//文件类型
$file_type = $info->getInfo('type');
// 输出扩展名 jpg
$extension = $info->getExtension();
// 输出 20160820/42a79759f284b767dfcb2a0197904287.jpg
$save_name = $info->getSaveName();
// 输出 42a79759f284b767dfcb2a0197904287.jpg
$new_filename = $info->getFilename();
$relative_url = $outer_req_url.'/'.str_replace('\\', '/', $save_name);
$result[] = [
'prefix'=>request()->domain().$outer_req_url,//访问前缀
'source_file_name'=>$source_file_name,
'file_type'=>$file_type,
'extension'=>$extension,
'save_name'=>str_replace('\\', '/', $save_name),
'new_filename'=>$new_filename,
'real_path'=>$this->dealPath($root_path.'public'.$relative_url),//物理路径
'outer_req_url'=>request()->domain().$relative_url,
'outer_req_relative_url'=>$relative_url,
'size'=>$info->getSize(),
];
}else{
//忽略错误
// 上传失败获取错误信息
$error[] = $file->getError();
}
}
$msg = '上传成功';
$code = 0;
if(count($error) > 0){
$msg = '上传失败;提示:'.implode(',', $error);//顺带返回错误信息
$code = 1;
}
return Tools::set_res($code, $msg, $result);
}
/**
* desc单文件或多文件上传至阿里云OSS服务
*
* authorwh
* @param array $config 配置
* //阿里云oss配置
'aliyun_oss_config' => [
//项目应用名称
'bucket'=>'wanlliuyinli-adm',//每创建一个bucket必须标明前缀代表这个bucket属于哪个项目
'UserPrincipalName'=>'wanghua@1113242774600735.onaliyun.com',
'Password'=>'0osE4cGo%tllnP1|uOQADPhM}Y?obR4U',
'AccessKeyId'=>'LTAI5tPqn1n7jugviVoGqFfa',
'AccessKeySecret'=>'BRoB5TdcUAFEuIR11BbN3R47Cm4Yep',
//https://help.aliyun.com/zh/oss/user-guide/regions-and-endpoints?spm=a2c4g.11186623.0.0.41ae2effA6oZar
'region_id'=>'cn-chengdu',//这里配置不能包含oss-前缀否则提示Invalid signing region in Authorization header
//西南1成都
//oss-cn-chengdu
//oss-cn-chengdu.aliyuncs.com
//oss-cn-chengdu-internal.aliyuncs.com
'endpoint'=>'oss-cn-chengdu.aliyuncs.com',//成都节点
'sts_endpoint'=>'sts.cn-chengdu.aliyuncs.com',//sts服务节点
]
* @param string $unique_id 唯一id可以是用户id可以是其它用于区分的标识默认存放在项目名称/控制器/方法/(唯一标识,可为空)/年月日/*.jpg
* @param string $file_upload_name 文件上传控件的name值必须带[]例如file_upload[]
* @param string $oss_type oss类型可选值ali_cloud 阿里云hua_wei 华为云(待扩展)
* @param int $size 单文件大小,默认10M
* @param string $ext 允许的文件扩展名
* @return array
*/
function filesUploadToAliCloudOss($config,$unique_id='',$file_upload_name = 'file_upload',$size=10, $ext=''){
return Mmodel::catch(function ()use ($config,$unique_id,$file_upload_name,$size,$ext){
$ini_upload_max_filesize = str_replace('M','',ini_get('upload_max_filesize'));
if(($size > $ini_upload_max_filesize)){
return Tools::set_res(1, '上传文件size受限不能超过upload_max_filesize='.ini_get('upload_max_filesize').'M');
}
// 获取表单上传文件
$files = request()->file($file_upload_name);
if(empty($files)){
return Tools::set_fail('请上传文件');
}
//扩展
if(empty($ext)) $ext = $this->file_ext;
//初始化阿里云OSS客户端
$oss_obj= new Objects($config);
//默认存储地址
$file_save_dir = $this->setFileSaveDir($unique_id);
$urls = [];
foreach($files as $file){
if(!$file->isValid()){
return Tools::set_fail('文件不合法');
}
$check_size = $file->checkSize($size*1024*1024);
if(!$check_size){
return Tools::set_fail('文件大小超限');
}
$check_res = $file->checkExt($ext);
if(!$check_res){
return Tools::set_fail('请上传合法文件');
}
$extension = explode('.',$file->getInfo('name'))[1];
$new_filename = 'file_'.Tools::rand_str(18).'.'.explode('.',$file->getInfo('name'))[1];
//必须加后缀
$filename = $file_save_dir.$new_filename;
// 从文件流上传
$res = $oss_obj->fileUpload($filename, $file->getInfo('tmp_name'));//上传之前的临时文件物理地址
$urls[] = [
'prefix'=>explode('.com',$res['info']['url'])[0].'.com',//访问前缀
'source_file_name'=>$file->getInfo('name'),//源文件名
'size'=>$file->getInfo('size'),//大小(b)
'extension'=>$extension,//扩展名
'file_type'=>$file->getInfo('type'),//文件后缀
'save_name'=>$filename,//保存文件名
'new_filename'=>$new_filename,//新文件名
'real_path'=>$res['info']['url'],//实际路径
'outer_req_url'=>$res['info']['url'],//外部访问地址
'outer_req_relative_url'=>$res['info']['url'],//外部访问相对地址
];
}
return Tools::set_ok('ok', $urls);
});
}
/**
* desc单文件上传至阿里云OSS
*
* authorwh
* @param array $config 配置
* //阿里云oss配置
'aliyun_oss_config' => [
//项目应用名称
'bucket'=>'wanlliuyinli-adm',//每创建一个bucket必须标明前缀代表这个bucket属于哪个项目
'UserPrincipalName'=>'wanghua@1113242774600735.onaliyun.com',
'Password'=>'0osE4cGo%tllnP1|uOQADPhM}Y?obR4U',
'AccessKeyId'=>'LTAI5tPqn1n7jugviVoGqFfa',
'AccessKeySecret'=>'BRoB5TdcUAFEuIR11BbN3R47Cm4Yep',
//https://help.aliyun.com/zh/oss/user-guide/regions-and-endpoints?spm=a2c4g.11186623.0.0.41ae2effA6oZar
'region_id'=>'cn-chengdu',//这里配置不能包含oss-前缀否则提示Invalid signing region in Authorization header
//西南1成都
//oss-cn-chengdu
//oss-cn-chengdu.aliyuncs.com
//oss-cn-chengdu-internal.aliyuncs.com
'endpoint'=>'oss-cn-chengdu.aliyuncs.com',//成都节点
'sts_endpoint'=>'sts.cn-chengdu.aliyuncs.com',//sts服务节点
]
* @param string $unique_id 唯一id可以是用户id可以是其它用于区分的标识默认存放在项目名称/控制器/方法/(唯一标识,可为空)/年月日/*.jpg
* @param string $file_upload_name 文件上传控件的name值例如file_upload
* @param string $oss_type oss类型可选值ali_cloud 阿里云hua_wei 华为云(待扩展)
* @param int $size 单文件大小,默认10M
* @param string $ext 允许的文件扩展名
* @return array
*/
function fileUploadToAliCloudOss($config,$unique_id='',$file_upload_name = 'file_upload',$size=10, $ext=''){
return Mmodel::catch(function ()use ($config,$unique_id,$file_upload_name,$size,$ext){
$ini_upload_max_filesize = str_replace('M','',ini_get('upload_max_filesize'));
if(($size > $ini_upload_max_filesize)){
return Tools::set_res(1, '上传文件size受限不能超过upload_max_filesize='.ini_get('upload_max_filesize').'M');
}
// 获取表单上传文件
$file = request()->file($file_upload_name);
if(empty($file)){
return Tools::set_fail('请上传文件');
}
//扩展
if(empty($ext)) $ext = $this->file_ext;
//初始化阿里云OSS客户端
$oss_obj= new Objects($config);
//默认存储地址
$file_save_dir = $this->setFileSaveDir($unique_id);
$urls = [];
if(!$file->isValid()){
return Tools::set_fail('文件不合法');
}
$check_size = $file->checkSize($size*1024*1024);
if(!$check_size){
return Tools::set_fail('文件大小超限');
}
$check_res = $file->checkExt($ext);
if(!$check_res){
return Tools::set_fail('请上传合法文件');
}
$extension = explode('.',$file->getInfo('name'))[1];
$new_filename = 'file_'.Tools::rand_str(18).'.'.explode('.',$file->getInfo('name'))[1];
//必须加后缀
$filename = $file_save_dir.$new_filename;
// 从文件流上传
$res = $oss_obj->fileUpload($filename, $file->getInfo('tmp_name'));//上传之前的临时文件物理地址
$urls[] = [
'prefix'=>explode('.com',$res['info']['url'])[0].'.com',//访问前缀
'source_file_name'=>$file->getInfo('name'),//源文件名
'size'=>$file->getInfo('size'),//大小(b)
'extension'=>$extension,//扩展名
'file_type'=>$file->getInfo('type'),//文件后缀
'save_name'=>$filename,//保存文件名
'new_filename'=>$new_filename,//新文件名
'real_path'=>$res['info']['url'],//实际路径
'outer_req_url'=>$res['info']['url'],//外部访问地址
'outer_req_relative_url'=>$res['info']['url'],//外部访问相对地址
];
return Tools::set_ok('ok', $urls);
});
}
/**
* desc设置文件保存目录
* authorwh
* @param string $unique_id 唯一id可以是用户id可以是其它用于区分的标识
* @return string
*/
function setFileSaveDir($unique_id=''){
if($unique_id){
$unique_id = $unique_id.'/';
}
//项目名称
$project_name = Tools::get_project_name();
//控制器方法路径
$ctl = strtolower(request()->controller().'/'.request()->action());
return $project_name.'/'.$ctl.'/'.$unique_id.date('Ymd').'/';//日期
}
/**
* desc检测size是否在ini配置范围
*
* authorwh
* @param $size
* @return bool
*/
private function uploadMaxFilesizeCheck($size){
$ini_upload_max_filesize = str_replace('M','',ini_get('upload_max_filesize'));
return $size<=$ini_upload_max_filesize;
}
/**
* desc上传base64格式图片【文件不用base64传输】
* authorwh
* @param string $file_upload_name
* @param int $size
* @param array $ext
* @return array
* @throws \Exception
*/
function uploadBase64Img($file_upload_name = 'file_upload', $size=10, $ext=''){
$source = input($file_upload_name, 'file_upload');
$file_size = strlen($source);
if($file_size > $size * 1024 * 1024){
return Tools::set_res(1, '上传失败,大小限制');
}
if(preg_match('/^(data:\s*image\/(\w+);base64,)/', $source, $result) < 1){
return Tools::set_res(1, '上传文件不合法');
}
$type = $result[2];
if(empty($ext)) $ext = $this->file_ext;
if(!in_array($type, explode($ext))){
return Tools::set_res(1, '上传失败,类型限制');
}
$result = base64_image_content($source);
if(false === $result){
return Tools::set_res(1, '上传失败');
}
return Tools::set_res(0, '上传成功', $result);
}
/**
* desc批量上传base64格式图片【文件不用base64传输】
* authorwh
* @param string $file_upload_name
* @param int $size
* @param array $ext
* @return array
* @throws \Exception
*/
function uploadBase64Imgs($file_upload_name = 'file_upload', $size=10, $ext=''){
$source_list = input($file_upload_name, 'file_upload');
if(!is_array($source_list)){
return Tools::set_res(1, '参数错误');
}
if(empty($ext)) $ext = $this->file_ext;
$result = [];
$error = [];
foreach ($source_list as $source){
$file_size = strlen($source);
if($file_size > $size * 1024 * 1024){
$error[] = '上传失败,大小限制';
continue;
}
if(preg_match('/^(data:\s*image\/(\w+);base64,)/', $source, $temp) < 1){
$error[] = '上传文件不合法';
continue;
}
$type = $temp[2];
if(!in_array($type, explode(',', $ext))){
$error[] = '类型限制';
continue;
}
$up_result = base64_image_content($source);
if(false === $up_result){
$error[] = '上传失败';
continue;
}
$result[] = $up_result;
}
$msg = '上传成功';
$code = 0;
if(count($error) > 0){
$msg = '上传失败;提示:'.implode(',', $error);//顺带返回错误信息
$code = 1;
}
return Tools::set_res($code, $msg, $result);
}
/**
* desc优化路径格式
* authorwh
*/
private function dealPath(string $path){
$str = str_replace('\\','/',$path);
return str_replace('//','/',$str);
}
/**
* desccurl上传文件php上传文件到远程服务器地址
*
* [php curl模拟文件上传]
*
* 依赖库:"guzzlehttp/guzzle": "^7.8",
*
* authorwh
* @param $url
* @param $params
* @param false[] $config
*/
function uploadCurlFiles($url,$params, $config=['verify' => false]){
if(empty($this->unique_key)){
throw new \Exception('UNIQUE_KEY 不能为空');
}
if(empty($this->file_lists_urls)){
throw new \Exception('文件地址列表不能为空');
}
// 创建 Guzzle HTTP 客户端实例
$client = new Client($config);
// 定义要上传的文件列表
$files = [
//[
// 'name' => 'file1', // 服务器接收的文件字段名
// 'contents' => Utils::tryFopen('D:\wanghua\projects\big_world_projects\universal_gravitation\public\uploads\20240506\dc1c441d81d48565ac6817b89d0f8bef.mp4', 'r'), // 文件路径
// 'filename' => 'dc1c441d81d48565ac6817b89d0f8bef.mp4', // 文件名,可自定义
//],
//[
// 'name' => 'files',
// 'contents' => Utils::tryFopen('D:\wanghua\projects\big_world_projects\universal_gravitation\public\uploads\20240506\f80179b23b60619fba8033bfd64f8817.mp4', 'r'),
// 'filename' => 'f80179b23b60619fba8033bfd64f8817.mp4',
//],
//['name'=>'prompt','contents'=>$prompt],
//['name'=>'tts_url','contents'=>$tts_url]
// 可以继续添加更多文件...
];
//print_r($params);
foreach ($params as $key=>$val){
$files[] = ['name'=>$key,'contents'=>$val];
}
$controller = strtolower(request()->controller());
$action = strtolower(request()->action());
$save_path = Tools::get_root_path() . "public/uploads/{$controller}/{$action}/".$this->unique_key;
if(!file_exists($save_path)){
mkdir($save_path,0777,true);
}
//文件远程可访问地址列表
foreach ($this->file_lists_urls as $name=>$video_url) {
//处理可用地址可能存储在本地或oss
$video_url = $this->get_usable_url($video_url);
$files[] = [
'name'=>"files",//服务器接收的文件字段名
//读取地址文件流(本地地址或远程地址均可,本地真实物理地址也行)
'contents'=>Utils::tryFopen($video_url, 'r'),
'filename'=>$name,//可自定义 文件名
];
}
// 构建多部分表单数据
$multipartStream = new MultipartStream($files);
$boundary = $multipartStream->getBoundary();
$headers = [
'Content-Type' => "multipart/form-data; boundary={$boundary}",
];
// 准备请求体
$body = $multipartStream->getContents();
// 发起 POST 请求
try {
$postinfo = [
'headers' => $headers,
'body' => $body,
];
//上传
$response = $client->request('POST', $url, $postinfo);
// 处理响应
//$responseBody = (string) $response->getBody();
// 检查状态码
//if ($response->getStatusCode() !== 200) {
// return false;
//}
return $response;//返回上传结果
} catch (\GuzzleHttp\Exception\RequestException $e) {
// 错误处理
//echo "Error: " . $e->getMessage();
Tools::error_txt_log($e);
return false;
}
}
/**
* desc获取视频可用地址兼容远程地址或本地地址
* authorwh
* @param $video_url
* @return string|string[]
*/
private function get_usable_url($video_url){
if(strpos($video_url,'http')!==false){
return $video_url;//远程地址
}
return Tools::get_root_path().$video_url;//本地地址
}
}

View File

@@ -0,0 +1 @@
## 文件上传类库

Some files were not shown because too many files have changed in this diff Show More