1
This commit is contained in:
25
.dockerignore
Normal file
25
.dockerignore
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/.idea
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
39
.gitignore
vendored
Normal file
39
.gitignore
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
#Ignore thumbnails created by Windows
|
||||||
|
Thumbs.db
|
||||||
|
#Ignore files built by Visual Studio
|
||||||
|
*.obj
|
||||||
|
*.exe
|
||||||
|
*.pdb
|
||||||
|
*.user
|
||||||
|
*.aps
|
||||||
|
*.pch
|
||||||
|
*.vspscc
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*.ncb
|
||||||
|
*.suo
|
||||||
|
*.tlb
|
||||||
|
*.tlh
|
||||||
|
*.bak
|
||||||
|
*.cache
|
||||||
|
*.ilk
|
||||||
|
*.log
|
||||||
|
[Bb]in
|
||||||
|
[Dd]ebug*/
|
||||||
|
*.lib
|
||||||
|
*.sbr
|
||||||
|
obj/
|
||||||
|
[Rr]elease*/
|
||||||
|
_ReSharper*/
|
||||||
|
[Tt]est[Rr]esult*
|
||||||
|
.vs/
|
||||||
|
#Nuget packages folder
|
||||||
|
packages/
|
||||||
|
.vscode/tasks.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/settings.json
|
||||||
|
*.orig
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
wwwroot/
|
||||||
38
.gitlab-ci.yml
Normal file
38
.gitlab-ci.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# This file is a template, and might need editing before it works on your project.
|
||||||
|
# This is a sample GitLab CI/CD configuration file that should run without any modifications.
|
||||||
|
# It demonstrates a basic 3 stage CI/CD pipeline. Instead of real tests or scripts,
|
||||||
|
# it uses echo commands to simulate the pipeline execution.
|
||||||
|
#
|
||||||
|
# A pipeline is composed of independent jobs that run scripts, grouped into stages.
|
||||||
|
# Stages run in sequential order, but jobs within stages run in parallel.
|
||||||
|
#
|
||||||
|
# For more information, see: https://docs.gitlab.com/ee/ci/yaml/index.html#stages
|
||||||
|
#
|
||||||
|
# You can copy and paste this template into a new `.gitlab-ci.yml` file.
|
||||||
|
# You should not add this template to an existing `.gitlab-ci.yml` file by using the `include:` keyword.
|
||||||
|
#
|
||||||
|
# To contribute improvements to CI/CD templates, please follow the Development guide at:
|
||||||
|
# https://docs.gitlab.com/ee/development/cicd/templates.html
|
||||||
|
# This specific template is located at:
|
||||||
|
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml
|
||||||
|
|
||||||
|
stages: # List of stages for jobs, and their order of execution
|
||||||
|
- build
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
build-job: # This job runs in the build stage, which runs first.
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- echo "starting build..."
|
||||||
|
- docker compose down || true
|
||||||
|
- docker rmi jd-coupons || true
|
||||||
|
- docker compose up --build -d
|
||||||
|
- echo "build completed."
|
||||||
|
|
||||||
|
deploy-job: # This job runs in the deploy stage.
|
||||||
|
stage: deploy # It only runs when *both* jobs in the test stage complete successfully.
|
||||||
|
environment: production
|
||||||
|
script:
|
||||||
|
- echo "deploying..."
|
||||||
|
- docker compose up -d
|
||||||
|
- echo "deploy completed."
|
||||||
28
Dockerfile
Normal file
28
Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
FROM node:22.12.0 AS frontend
|
||||||
|
RUN npm install -g vite
|
||||||
|
COPY . .
|
||||||
|
WORKDIR /src/Front
|
||||||
|
RUN npm i
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||||
|
USER $APP_UID
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN dotnet restore "/src/Web/Web.csproj"
|
||||||
|
WORKDIR "/src/Web"
|
||||||
|
RUN dotnet build "Web.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "Web.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "Web.dll"]
|
||||||
93
README.md
Normal file
93
README.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# JdCoupons
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
||||||
|
|
||||||
|
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
||||||
|
|
||||||
|
## Add your files
|
||||||
|
|
||||||
|
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
||||||
|
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd existing_repo
|
||||||
|
git remote add origin http://c1283ff797ac/suncheng/jdcoupons.git
|
||||||
|
git branch -M main
|
||||||
|
git push -uf origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integrate with your tools
|
||||||
|
|
||||||
|
- [ ] [Set up project integrations](http://c1283ff797ac/suncheng/jdcoupons/-/settings/integrations)
|
||||||
|
|
||||||
|
## Collaborate with your team
|
||||||
|
|
||||||
|
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
||||||
|
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
||||||
|
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
||||||
|
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
||||||
|
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
|
||||||
|
|
||||||
|
## Test and Deploy
|
||||||
|
|
||||||
|
Use the built-in continuous integration in GitLab.
|
||||||
|
|
||||||
|
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
|
||||||
|
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
||||||
|
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
||||||
|
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
||||||
|
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
# Editing this README
|
||||||
|
|
||||||
|
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
||||||
|
|
||||||
|
## Suggestions for a good README
|
||||||
|
|
||||||
|
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
||||||
|
|
||||||
|
## Name
|
||||||
|
Choose a self-explaining name for your project.
|
||||||
|
|
||||||
|
## Description
|
||||||
|
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
||||||
|
|
||||||
|
## Badges
|
||||||
|
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
||||||
|
|
||||||
|
## Visuals
|
||||||
|
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
State if you are open to contributions and what your requirements are for accepting them.
|
||||||
|
|
||||||
|
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
||||||
|
|
||||||
|
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
||||||
|
|
||||||
|
## Authors and acknowledgment
|
||||||
|
Show your appreciation to those who have contributed to the project.
|
||||||
|
|
||||||
|
## License
|
||||||
|
For open source projects, say how it is licensed.
|
||||||
|
|
||||||
|
## Project status
|
||||||
|
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
||||||
54
docker-compose.yaml
Normal file
54
docker-compose.yaml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
services:
|
||||||
|
jd-coupons-proxy:
|
||||||
|
image: beevelop/nginx-basic-auth:latest
|
||||||
|
container_name: jd-coupons-proxy
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- all_in
|
||||||
|
ports:
|
||||||
|
- "27482:80"
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
- HTPASSWD=suncheng:$$apr1$$2QX32QHP$$HIGAbCuTt8jxdc4uDzNLI1
|
||||||
|
- FORWARD_PORT=8080
|
||||||
|
- FORWARD_HOST=jd-coupons
|
||||||
|
|
||||||
|
jd-h5st-proxy:
|
||||||
|
image: beevelop/nginx-basic-auth:latest
|
||||||
|
container_name: jd-h5st-proxy
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- all_in
|
||||||
|
ports:
|
||||||
|
- "27483:80"
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
- HTPASSWD=suncheng:$$apr1$$2QX32QHP$$HIGAbCuTt8jxdc4uDzNLI1
|
||||||
|
- FORWARD_PORT=3001
|
||||||
|
- FORWARD_HOST=jd-h5st
|
||||||
|
|
||||||
|
jd-coupons:
|
||||||
|
image: jd-coupons
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
container_name: jd-coupons
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- all_in
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
jd-h5st:
|
||||||
|
image: youfak/jd_h5st_server:latest
|
||||||
|
container_name: jd-h5st
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- all_in
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
networks:
|
||||||
|
all_in:
|
||||||
|
external: true
|
||||||
|
|
||||||
31
jdcoupons.sln
Normal file
31
jdcoupons.sln
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{22C5F48D-3205-4A5D-A814-144C181C520C}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
docker-compose.yaml = docker-compose.yaml
|
||||||
|
.gitlab-ci.yml = .gitlab-ci.yml
|
||||||
|
Dockerfile = Dockerfile
|
||||||
|
.dockerignore = .dockerignore
|
||||||
|
.gitignore = .gitignore
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "src\Web\Web.csproj", "{66A8DE76-1439-4BB5-861D-E37BDD75E350}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{66A8DE76-1439-4BB5-861D-E37BDD75E350}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{66A8DE76-1439-4BB5-861D-E37BDD75E350}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{66A8DE76-1439-4BB5-861D-E37BDD75E350}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{66A8DE76-1439-4BB5-861D-E37BDD75E350}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
6
src/Front/.editorconfig
Normal file
6
src/Front/.editorconfig
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
|
||||||
|
charset = utf-8
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
30
src/Front/.gitignore
vendored
Normal file
30
src/Front/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
7
src/Front/.prettierrc.json
Normal file
7
src/Front/.prettierrc.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
8
src/Front/.vscode/extensions.json
vendored
Normal file
8
src/Front/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"EditorConfig.EditorConfig",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
35
src/Front/README.md
Normal file
35
src/Front/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# test
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint with [ESLint](https://eslint.org/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
19
src/Front/eslint.config.js
Normal file
19
src/Front/eslint.config.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import pluginVue from 'eslint-plugin-vue'
|
||||||
|
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
name: 'app/files-to-lint',
|
||||||
|
files: ['**/*.{js,mjs,jsx,vue}'],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'app/files-to-ignore',
|
||||||
|
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
|
||||||
|
},
|
||||||
|
|
||||||
|
js.configs.recommended,
|
||||||
|
...pluginVue.configs['flat/essential'],
|
||||||
|
skipFormatting,
|
||||||
|
]
|
||||||
13
src/Front/index.html
Normal file
13
src/Front/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
src/Front/jsconfig.json
Normal file
8
src/Front/jsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
2581
src/Front/package-lock.json
generated
Normal file
2581
src/Front/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
src/Front/package.json
Normal file
29
src/Front/package.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint . --fix",
|
||||||
|
"format": "prettier --write src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"element-plus": "^2.9.0",
|
||||||
|
"pinia": "^2.2.6",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-router": "^4.4.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.14.0",
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"@vue/eslint-config-prettier": "^10.1.0",
|
||||||
|
"eslint": "^9.14.0",
|
||||||
|
"eslint-plugin-vue": "^9.30.0",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
|
"vite": "^6.0.1",
|
||||||
|
"vite-plugin-vue-devtools": "^7.6.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/Front/public/favicon.ico
Normal file
BIN
src/Front/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
39
src/Front/src/App.vue
Normal file
39
src/Front/src/App.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<script setup>
|
||||||
|
import { RouterLink, RouterView } from 'vue-router'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-container style="height: 100vh;width: 100vw">
|
||||||
|
<el-header>
|
||||||
|
|
||||||
|
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||||
|
<el-tab-pane label="Cookie" name="cookie"></el-tab-pane>
|
||||||
|
<el-tab-pane label="店铺" name="store"></el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<RouterView />
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeName: 'cookie'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick(tab, event) {
|
||||||
|
console.log(tab,event);
|
||||||
|
RouterLink.push({ name: tab.name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
85
src/Front/src/assets/base.css
Normal file
85
src/Front/src/assets/base.css
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/* color palette from <https://github.com/vuejs/theme> */
|
||||||
|
:root {
|
||||||
|
--vt-c-white: #ffffff;
|
||||||
|
--vt-c-white-soft: #f8f8f8;
|
||||||
|
--vt-c-white-mute: #f2f2f2;
|
||||||
|
|
||||||
|
--vt-c-black: #181818;
|
||||||
|
--vt-c-black-soft: #222222;
|
||||||
|
--vt-c-black-mute: #282828;
|
||||||
|
|
||||||
|
--vt-c-indigo: #2c3e50;
|
||||||
|
|
||||||
|
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||||
|
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||||
|
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||||
|
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||||
|
|
||||||
|
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||||
|
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||||
|
--vt-c-text-dark-1: var(--vt-c-white);
|
||||||
|
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* semantic color variables for this project */
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-white);
|
||||||
|
--color-background-soft: var(--vt-c-white-soft);
|
||||||
|
--color-background-mute: var(--vt-c-white-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-light-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-light-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-light-1);
|
||||||
|
--color-text: var(--vt-c-text-light-1);
|
||||||
|
|
||||||
|
--section-gap: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-black);
|
||||||
|
--color-background-soft: var(--vt-c-black-soft);
|
||||||
|
--color-background-mute: var(--vt-c-black-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-dark-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-dark-1);
|
||||||
|
--color-text: var(--vt-c-text-dark-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
color: var(--color-text);
|
||||||
|
transition:
|
||||||
|
color 0.5s,
|
||||||
|
background-color 0.5s;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family:
|
||||||
|
Inter,
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
'Segoe UI',
|
||||||
|
Roboto,
|
||||||
|
Oxygen,
|
||||||
|
Ubuntu,
|
||||||
|
Cantarell,
|
||||||
|
'Fira Sans',
|
||||||
|
'Droid Sans',
|
||||||
|
'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
font-size: 15px;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
1
src/Front/src/assets/logo.svg
Normal file
1
src/Front/src/assets/logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||||
|
After Width: | Height: | Size: 276 B |
35
src/Front/src/assets/main.css
Normal file
35
src/Front/src/assets/main.css
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
@import './base.css';
|
||||||
|
|
||||||
|
#app {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
.green {
|
||||||
|
text-decoration: none;
|
||||||
|
color: hsla(160, 100%, 37%, 1);
|
||||||
|
transition: 0.4s;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
a:hover {
|
||||||
|
background-color: hsla(160, 100%, 37%, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
padding: 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/Front/src/main.js
Normal file
17
src/Front/src/main.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import './assets/main.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
app.use(ElementPlus)
|
||||||
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
23
src/Front/src/router/index.js
Normal file
23
src/Front/src/router/index.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import HomeView from '../views/HomeView.vue'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: HomeView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'about',
|
||||||
|
// route level code-splitting
|
||||||
|
// this generates a separate chunk (About.[hash].js) for this route
|
||||||
|
// which is lazy-loaded when the route is visited.
|
||||||
|
component: () => import('../views/AboutView.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
12
src/Front/src/stores/counter.js
Normal file
12
src/Front/src/stores/counter.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const doubleCount = computed(() => count.value * 2)
|
||||||
|
function increment() {
|
||||||
|
count.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
return { count, doubleCount, increment }
|
||||||
|
})
|
||||||
9
src/Front/src/views/AboutView.vue
Normal file
9
src/Front/src/views/AboutView.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
2
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
3
src/Front/src/views/HomeView.vue
Normal file
3
src/Front/src/views/HomeView.vue
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<div>1</div>
|
||||||
|
</template>
|
||||||
21
src/Front/vite.config.js
Normal file
21
src/Front/vite.config.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
outDir: '../Web/wwwroot',
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueDevTools(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
42
src/Web/Controllers/ManualController.cs
Normal file
42
src/Web/Controllers/ManualController.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Web.Services;
|
||||||
|
|
||||||
|
namespace Web.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]/[action]")]
|
||||||
|
public class ManualController(
|
||||||
|
IManualService manualService
|
||||||
|
) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<string> GetServiceTimeAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var time = await manualService.GetServiceTimeAsync();
|
||||||
|
|
||||||
|
return time.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("GetServiceTimeAsync failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<string> GetCouponAsync([FromQuery] string cookieName, [FromQuery] string storeName, [FromQuery] int couponIndex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await manualService.GetCoupon(cookieName, storeName, couponIndex);
|
||||||
|
|
||||||
|
return JsonConvert.SerializeObject(result, Formatting.Indented);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("GetCoupon failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/Web/Interfaces/Jd/Dto/CouponParamDto.cs
Normal file
10
src/Web/Interfaces/Jd/Dto/CouponParamDto.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Web.Interfaces.Jd.Dto;
|
||||||
|
|
||||||
|
public record CouponParamDto
|
||||||
|
{
|
||||||
|
public string StoreUrl { get; set; } = null!;
|
||||||
|
|
||||||
|
public string CouponUrl { get; set; } = null!;
|
||||||
|
|
||||||
|
public string Cookie { get; set; } = null!;
|
||||||
|
}
|
||||||
29
src/Web/Interfaces/Jd/Dto/CouponResultDto.cs
Normal file
29
src/Web/Interfaces/Jd/Dto/CouponResultDto.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
namespace Web.Interfaces.Jd.Dto;
|
||||||
|
|
||||||
|
public record CouponResultDto
|
||||||
|
{
|
||||||
|
public int? Code { get; set; }
|
||||||
|
|
||||||
|
public CouponResultDataDto? Data { get; set; }
|
||||||
|
|
||||||
|
public string? Message { get; set; }
|
||||||
|
|
||||||
|
public int? SubCode { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CouponResultDataDto
|
||||||
|
{
|
||||||
|
public long? BatchId { get; set; }
|
||||||
|
|
||||||
|
public decimal? Discount { get; set; }
|
||||||
|
|
||||||
|
public decimal? Quota { get; set; }
|
||||||
|
|
||||||
|
public long? BeginTime { get; set; }
|
||||||
|
|
||||||
|
public long? EndTime { get; set; }
|
||||||
|
|
||||||
|
public long ResultCode { get; set; }
|
||||||
|
|
||||||
|
public string ResultMsg { get; set; } = null!;
|
||||||
|
}
|
||||||
283
src/Web/Interfaces/Jd/JdInterface.cs
Normal file
283
src/Web/Interfaces/Jd/JdInterface.cs
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Web.Interfaces.Jd.Dto;
|
||||||
|
using Web.Utils;
|
||||||
|
|
||||||
|
namespace Web.Interfaces.Jd;
|
||||||
|
|
||||||
|
public interface IJdInterface
|
||||||
|
{
|
||||||
|
Task<DateTime> GetServiceTimeAsync();
|
||||||
|
|
||||||
|
Task<CouponResultDto?> GetCouponAsync(CouponParamDto param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JdInterface(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
ILogger<JdInterface> logger
|
||||||
|
) : IJdInterface
|
||||||
|
{
|
||||||
|
public async Task<DateTime> GetServiceTimeAsync()
|
||||||
|
{
|
||||||
|
var client = httpClientFactory.CreateClient(Constants.JdMobileApiClient);
|
||||||
|
|
||||||
|
var postContent = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "appid", "yinliu" },
|
||||||
|
{ "functionId", "yinliu_service_display" },
|
||||||
|
{ "loginType", "2" },
|
||||||
|
{ "_", GetTimespan().ToString() },
|
||||||
|
{ "cthr", "1" },
|
||||||
|
{ "body", "{\"busUrl\":\"https://m.jd.com/\",\"functionName\":\"DISPLAY\"}" }
|
||||||
|
});
|
||||||
|
|
||||||
|
postContent.Headers.Add("origin", "https://m.jd.com");
|
||||||
|
postContent.Headers.Add("sec-fetch-dest", "empty");
|
||||||
|
postContent.Headers.Add("sec-fetch-mode", "cors");
|
||||||
|
postContent.Headers.Add("sec-fetch-site", "same-site");
|
||||||
|
postContent.Headers.Add("x-referer-page", "https://m.jd.com/");
|
||||||
|
|
||||||
|
var response = await client.PostAsync("/", postContent);
|
||||||
|
|
||||||
|
var responseHeaders = response.Headers;
|
||||||
|
|
||||||
|
if (!responseHeaders.TryGetValues("x-api-request-id", out var dateValues))
|
||||||
|
{
|
||||||
|
logger.LogError("x-api-request-id not found, 使用本地时间");
|
||||||
|
return DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
var param = dateValues.FirstOrDefault()?.Split("-", StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
if (param is not { Length: 3 })
|
||||||
|
{
|
||||||
|
logger.LogError("x-api-request-id 格式错误, 使用本地时间");
|
||||||
|
return DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!long.TryParse(param[2], out var time))
|
||||||
|
{
|
||||||
|
logger.LogError("x-api-request-id 时间戳解析失败, 使用本地时间");
|
||||||
|
return DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation($"x-api-request-id: {time}, 与本地时间差: {time - GetTimespan()}");
|
||||||
|
|
||||||
|
return DateTimeOffset.FromUnixTimeMilliseconds(time).LocalDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CouponResultDto?> GetCouponAsync(CouponParamDto param)
|
||||||
|
{
|
||||||
|
var vender = new Uri(param.StoreUrl).Query.Split(["&", "?"], StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(x => x.Contains("venderId="));
|
||||||
|
if (vender == null)
|
||||||
|
{
|
||||||
|
throw new Exception("venderid not found by store url \r\n" + param.StoreUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var venderid = vender.Split("=")[1];
|
||||||
|
if (int.TryParse(venderid, out var venderidInt) == false)
|
||||||
|
{
|
||||||
|
throw new Exception("venderid value not found by store url \r\n" + param.StoreUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var couponUrl = new Uri(param.CouponUrl);
|
||||||
|
var to = couponUrl.Query.Split(["&", "?"], StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(x => x.Contains("to="));
|
||||||
|
if (to == null)
|
||||||
|
{
|
||||||
|
throw new Exception("to not found by coupon url \r\n" + param.CouponUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var toValue = to.Split("=")[1];
|
||||||
|
if (string.IsNullOrEmpty(toValue))
|
||||||
|
{
|
||||||
|
throw new Exception("to value not found by coupon url \r\n" + param.CouponUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var roleId = couponUrl.Query.Split(["&", "?"], StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(x => x.Contains("roleId="));
|
||||||
|
if (roleId == null)
|
||||||
|
{
|
||||||
|
throw new Exception("roleId not found by coupon url \r\n" + param.CouponUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var roleIdValue = roleId.Split("=")[1];
|
||||||
|
if (string.IsNullOrEmpty(roleIdValue))
|
||||||
|
{
|
||||||
|
throw new Exception("roleId value not found by coupon url \r\n" + param.CouponUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = couponUrl.Query.Split(["&", "?"], StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(x => x.Contains("key="));
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
throw new Exception("key not found by coupon url \r\n" + param.CouponUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyValue = key.Split("=")[1];
|
||||||
|
if (string.IsNullOrEmpty(keyValue))
|
||||||
|
{
|
||||||
|
throw new Exception("key value not found by coupon url \r\n" + param.CouponUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = new
|
||||||
|
{
|
||||||
|
key = keyValue,
|
||||||
|
roleId = roleIdValue,
|
||||||
|
linkKey = "",
|
||||||
|
to = toValue,
|
||||||
|
venderid = venderidInt
|
||||||
|
};
|
||||||
|
|
||||||
|
var appid = "h5_awake_wxapp";
|
||||||
|
var functionId = "mcoupon_getcoupon";
|
||||||
|
|
||||||
|
var cookieDic = GetCookieDic(param.Cookie);
|
||||||
|
|
||||||
|
var client = httpClientFactory.CreateClient(Constants.JdMobileApiClient);
|
||||||
|
|
||||||
|
var ua = client.DefaultRequestHeaders.UserAgent.ToString();
|
||||||
|
|
||||||
|
var (h5St, token, appCode) = await GetH5St(body, ua, cookieDic);
|
||||||
|
|
||||||
|
// post url 参数传递
|
||||||
|
var postContent = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "appid", appid },
|
||||||
|
{ "functionId", functionId },
|
||||||
|
{ "body", JsonConvert.SerializeObject(body) },
|
||||||
|
{ "h5st", h5St },
|
||||||
|
{ "x-api-eid-token", token },
|
||||||
|
{ "loginType", "2" },
|
||||||
|
{ "client", "wh5" },
|
||||||
|
{ "t", GetTimespan().ToString() },
|
||||||
|
{ "_stk", "appid,body,client,functionId,t" },
|
||||||
|
{ "_ste", "1" },
|
||||||
|
{ "g_login_type", "1" },
|
||||||
|
{ "appCode", appCode },
|
||||||
|
{ "g_ty", "ajax" },
|
||||||
|
{ "_", GetTimespan().ToString() },
|
||||||
|
{ "sceneval", "2" }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置
|
||||||
|
postContent.Headers.Add("authority", "api.m.jd.com");
|
||||||
|
postContent.Headers.Add("origin", "https://coupon.m.jd.com");
|
||||||
|
postContent.Headers.Add("sec-fetch-dest", "empty");
|
||||||
|
postContent.Headers.Add("sec-fetch-mode", "cors");
|
||||||
|
postContent.Headers.Add("sec-fetch-site", "same-site");
|
||||||
|
postContent.Headers.Add("x-referer-page", "https://coupon.m.jd.com/coupons/show.action");
|
||||||
|
postContent.Headers.Add("x-rp-client", "h5_1.0.0");
|
||||||
|
postContent.Headers.Add("cookie", cookieDic.Select(x => $"{x.Key}={x.Value}").ToArray());
|
||||||
|
postContent.Headers.Add("priority", "u=1, i");
|
||||||
|
|
||||||
|
var apiUrl = "https://api.m.jd.com/client.action";
|
||||||
|
|
||||||
|
var apiResponse = await client.PostAsync(apiUrl, postContent);
|
||||||
|
|
||||||
|
var contentEncoding = apiResponse.Content.Headers.ContentEncoding;
|
||||||
|
var responseBytes = await apiResponse.Content.ReadAsByteArrayAsync();
|
||||||
|
|
||||||
|
string apiResponseSting;
|
||||||
|
|
||||||
|
if (contentEncoding.Contains("gzip"))
|
||||||
|
{
|
||||||
|
using var compressedStream = new MemoryStream(responseBytes);
|
||||||
|
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
|
||||||
|
using var reader = new StreamReader(decompressionStream, Encoding.UTF8);
|
||||||
|
apiResponseSting = await reader.ReadToEndAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
apiResponseSting = Encoding.UTF8.GetString(responseBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonConvert.DeserializeObject<CouponResultDto>(apiResponseSting);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<(
|
||||||
|
string h5st,
|
||||||
|
string token,
|
||||||
|
string appCode
|
||||||
|
)> GetH5St(
|
||||||
|
object body,
|
||||||
|
string ua,
|
||||||
|
IDictionary<string, string> cookieDic)
|
||||||
|
{
|
||||||
|
using var client = httpClientFactory.CreateClient(Constants.H5StClient);
|
||||||
|
|
||||||
|
var appid = "h5_awake_wxapp";
|
||||||
|
var functionId = "mcoupon_getcoupon";
|
||||||
|
|
||||||
|
if (cookieDic.TryGetValue("cd_eid", out var token) == false
|
||||||
|
|| string.IsNullOrWhiteSpace(token))
|
||||||
|
{
|
||||||
|
throw new Exception("cookie cd_eid not found by cookie \r\n" + string.Join("\r\n", cookieDic.Select(x => $"{x.Key}={x.Value}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookieDic.TryGetValue("pt_pin", out var pin) == false
|
||||||
|
|| string.IsNullOrWhiteSpace(pin))
|
||||||
|
{
|
||||||
|
throw new Exception("cookie pt_pin not found by cookie \r\n" + string.Join("\r\n", cookieDic.Select(x => $"{x.Key}={x.Value}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookieDic.TryGetValue("appCode", out var appCode) == false
|
||||||
|
|| string.IsNullOrWhiteSpace(appCode))
|
||||||
|
{
|
||||||
|
throw new Exception("cookie appCode not found by cookie \r\n" + string.Join("\r\n", cookieDic.Select(x => $"{x.Key}={x.Value}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = new
|
||||||
|
{
|
||||||
|
client = "wh5",
|
||||||
|
appId = appid,
|
||||||
|
version = "4.9.1",
|
||||||
|
pin,
|
||||||
|
ua,
|
||||||
|
body = new
|
||||||
|
{
|
||||||
|
functionId,
|
||||||
|
appid = appCode,
|
||||||
|
body = JsonConvert.SerializeObject(body)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var requestContent = JsonContent.Create(request);
|
||||||
|
|
||||||
|
var h5StResponse = await client.PostAsync("/h5st", requestContent);
|
||||||
|
|
||||||
|
var h5StJson = await h5StResponse.Content.ReadAsStringAsync();
|
||||||
|
var h5StObj = JsonConvert.DeserializeObject<JObject>(h5StJson);
|
||||||
|
|
||||||
|
var h5St = h5StObj?["body"]?["h5st"]?["h5st"]?.ToString();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(h5St))
|
||||||
|
{
|
||||||
|
throw new Exception("h5st not found by h5st response \r\n" + h5StJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
h5St,
|
||||||
|
token,
|
||||||
|
appCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IDictionary<string, string> GetCookieDic(string cookie)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var arr = cookie.Split(";").Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x));
|
||||||
|
|
||||||
|
return arr.Select(x => x.Split("=")).ToDictionary(x => x[0], x => x[1]);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("cookie error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long GetTimespan()
|
||||||
|
{
|
||||||
|
return DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||||
|
}
|
||||||
|
}
|
||||||
92
src/Web/Program.cs
Normal file
92
src/Web/Program.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Web.Interfaces.Jd;
|
||||||
|
using Web.Utils;
|
||||||
|
using Web.Services;
|
||||||
|
using Web.Services.Dto;
|
||||||
|
using Web.Stores;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<ConfigStore>(services =>
|
||||||
|
{
|
||||||
|
// 从 wwwroot/config.json中加载配置到 stores
|
||||||
|
var config = File.ReadAllText("Stores/config.json");
|
||||||
|
var configStore = JsonConvert.DeserializeObject<JObject>(config);
|
||||||
|
|
||||||
|
if (configStore == null)
|
||||||
|
{
|
||||||
|
throw new Exception("config.json is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookies = configStore["cookies"]?.ToObject<CookieDto[]>();
|
||||||
|
var stores = configStore["stores"]?.ToObject<StoreDto[]>();
|
||||||
|
|
||||||
|
if (cookies == null || stores == null)
|
||||||
|
{
|
||||||
|
throw new Exception("cookies or stores is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigStore store = new();
|
||||||
|
store.Init(cookies, stores);
|
||||||
|
return store;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<IManualService, ManualService>();
|
||||||
|
builder.Services.AddSingleton<IJdInterface, JdInterface>();
|
||||||
|
builder.Services.AddHostedService<JobTimeService>();
|
||||||
|
builder.Services.AddHostedService<JobService>();
|
||||||
|
|
||||||
|
builder.Services.AddHttpClient(Constants.JdMobileApiClient, client =>
|
||||||
|
{
|
||||||
|
client.BaseAddress = new Uri("https://api.m.jd.com");
|
||||||
|
client.DefaultRequestHeaders.Add("accept", "application/json");
|
||||||
|
client.DefaultRequestHeaders.Add("accept-encoding", "gzip, deflate, br, zstd");
|
||||||
|
client.DefaultRequestHeaders.Add("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
|
||||||
|
client.DefaultRequestHeaders.Add("referer", "https://coupon.m.jd.com");
|
||||||
|
client.DefaultRequestHeaders.Add("user-agent", RandomUtil.GetUserAgent()); // TODO 研究下 ua 的刷新时机
|
||||||
|
});
|
||||||
|
builder.Services.AddHttpClient(Constants.H5StClient, (provider, client) =>
|
||||||
|
{
|
||||||
|
var configuration = provider.GetRequiredService<IConfiguration>();
|
||||||
|
|
||||||
|
var host = configuration["Interfaces:H5StService:Url"];
|
||||||
|
var username = configuration["Interfaces:H5StService:UserName"];
|
||||||
|
var password = configuration["Interfaces:H5StService:Password"];
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(host))
|
||||||
|
{
|
||||||
|
throw new Exception("Interfaces:H5StService is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(username)
|
||||||
|
&& !string.IsNullOrEmpty(password))
|
||||||
|
{
|
||||||
|
var auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.BaseAddress = new Uri(host);
|
||||||
|
});
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
|
||||||
|
app.UseDefaultFiles();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
14
src/Web/Properties/launchSettings.json
Normal file
14
src/Web/Properties/launchSettings.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "http://localhost:5017",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/Web/Services/Dto/CookieDto.cs
Normal file
21
src/Web/Services/Dto/CookieDto.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
namespace Web.Services.Dto;
|
||||||
|
|
||||||
|
public class CookieDto
|
||||||
|
{
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
|
||||||
|
public string Desc { get; set; } = null!;
|
||||||
|
|
||||||
|
public string Cookie { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StoreDto
|
||||||
|
{
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
|
||||||
|
public string Desc { get; set; } = null!;
|
||||||
|
|
||||||
|
public string Url { get; set; } = null!;
|
||||||
|
|
||||||
|
public (string url, string cron)[] Coupons { get; set; } = [];
|
||||||
|
}
|
||||||
127
src/Web/Services/JobService.cs
Normal file
127
src/Web/Services/JobService.cs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
using Cronos;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Web.Interfaces.Jd;
|
||||||
|
using Web.Interfaces.Jd.Dto;
|
||||||
|
using Web.Stores;
|
||||||
|
|
||||||
|
namespace Web.Services;
|
||||||
|
|
||||||
|
public class JobService(
|
||||||
|
ConfigStore configDto,
|
||||||
|
IJdInterface jdInterface,
|
||||||
|
ILogger<JobService> logger
|
||||||
|
) : BackgroundService
|
||||||
|
{
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await DoWork();
|
||||||
|
|
||||||
|
// 每100ms 再执行下一轮
|
||||||
|
await Task.Delay(100, stoppingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<(string storeDesc, string coupon), DateTime> _lastRunTimes = new();
|
||||||
|
|
||||||
|
private async Task DoWork()
|
||||||
|
{
|
||||||
|
foreach (var store in configDto.Stores)
|
||||||
|
{
|
||||||
|
foreach (var coupon in store.Coupons)
|
||||||
|
{
|
||||||
|
if (!_lastRunTimes.ContainsKey((store.Desc, coupon.url)))
|
||||||
|
{
|
||||||
|
_lastRunTimes.Add((store.Desc, coupon.url), default);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cron = GetCronExpression(coupon.cron);
|
||||||
|
|
||||||
|
if (cron == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下次执行时间赋值 为jd时间-1s 后计算 cron 时间
|
||||||
|
|
||||||
|
var nextRunTime = cron.GetNextOccurrence(JdTime.ServiceTime)?.AddSeconds(-1);
|
||||||
|
|
||||||
|
if (nextRunTime == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation($"下次执行时间: {nextRunTime.Value} , {store.Desc} , {coupon.url}");
|
||||||
|
|
||||||
|
_lastRunTimes[(store.Desc, coupon.url)] = nextRunTime.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tasks = new List<(object request, Task<CouponResultDto?> response)>();
|
||||||
|
|
||||||
|
foreach (var item in _lastRunTimes)
|
||||||
|
{
|
||||||
|
if (item.Value > JdTime.ServiceTime || item.Value == default)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var cookie in configDto.Cookies)
|
||||||
|
{
|
||||||
|
var storeUrl = configDto.Stores.FirstOrDefault(x => x.Desc == item.Key.storeDesc)?.Url;
|
||||||
|
|
||||||
|
if (storeUrl == null)
|
||||||
|
{
|
||||||
|
logger.LogError("storeUrl not found");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var task = jdInterface.GetCouponAsync(new CouponParamDto
|
||||||
|
{
|
||||||
|
Cookie = cookie.Cookie,
|
||||||
|
CouponUrl = item.Key.coupon,
|
||||||
|
StoreUrl = storeUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
tasks.Add((new
|
||||||
|
{
|
||||||
|
cookie = cookie.Desc,
|
||||||
|
store = item.Key.storeDesc,
|
||||||
|
item.Key.coupon,
|
||||||
|
DateTime.Now
|
||||||
|
}, task));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tasks.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks.Select(x => x.response));
|
||||||
|
|
||||||
|
foreach (var task in tasks)
|
||||||
|
{
|
||||||
|
if (await task.response == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation($"领取优惠券结果: {JsonConvert.SerializeObject(task.request)} , {await task.response}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CronExpression? GetCronExpression(string cron)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return CronExpression.Parse(cron);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "GetCronExpression failed");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/Web/Services/JobTimeService.cs
Normal file
22
src/Web/Services/JobTimeService.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Web.Interfaces.Jd;
|
||||||
|
using Web.Stores;
|
||||||
|
|
||||||
|
namespace Web.Services;
|
||||||
|
|
||||||
|
public class JobTimeService(
|
||||||
|
IJdInterface jdInterface
|
||||||
|
) : BackgroundService
|
||||||
|
{
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var jdTime = await jdInterface.GetServiceTimeAsync();
|
||||||
|
|
||||||
|
JdTime.ServiceTime = jdTime;
|
||||||
|
|
||||||
|
// 每分钟执行一次
|
||||||
|
await Task.Delay(60000, stoppingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/Web/Services/ManualOperation.cs
Normal file
54
src/Web/Services/ManualOperation.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using Web.Interfaces.Jd;
|
||||||
|
using Web.Interfaces.Jd.Dto;
|
||||||
|
using Web.Stores;
|
||||||
|
|
||||||
|
namespace Web.Services;
|
||||||
|
|
||||||
|
public interface IManualService
|
||||||
|
{
|
||||||
|
Task<CouponResultDto?> GetCoupon(string cookieName, string storeName, int couponIndex);
|
||||||
|
|
||||||
|
Task<DateTime> GetServiceTimeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ManualService(
|
||||||
|
ConfigStore configStore,
|
||||||
|
IJdInterface jdInterface
|
||||||
|
) : IManualService
|
||||||
|
{
|
||||||
|
public async Task<CouponResultDto?> GetCoupon(string cookieName, string storeName, int couponIndex)
|
||||||
|
{
|
||||||
|
var cookie = configStore.Cookies.FirstOrDefault(x => x.Desc == cookieName)?.Cookie;
|
||||||
|
if (string.IsNullOrEmpty(cookie))
|
||||||
|
{
|
||||||
|
throw new Exception("cookie not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
var store = configStore.Stores.FirstOrDefault(x => x.Desc == storeName);
|
||||||
|
if (store == null)
|
||||||
|
{
|
||||||
|
throw new Exception("store not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (couponIndex < 0 || couponIndex >= store.Coupons.Length)
|
||||||
|
{
|
||||||
|
throw new Exception("coupon index out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
var coupon = store.Coupons[couponIndex];
|
||||||
|
|
||||||
|
var result = await jdInterface.GetCouponAsync(new CouponParamDto
|
||||||
|
{
|
||||||
|
Cookie = cookie,
|
||||||
|
CouponUrl = coupon.url,
|
||||||
|
StoreUrl = store.Url
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DateTime> GetServiceTimeAsync()
|
||||||
|
{
|
||||||
|
return await jdInterface.GetServiceTimeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/Web/Stores/ConfigStore.cs
Normal file
21
src/Web/Stores/ConfigStore.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Web.Services.Dto;
|
||||||
|
|
||||||
|
namespace Web.Stores;
|
||||||
|
|
||||||
|
public class ConfigStore
|
||||||
|
{
|
||||||
|
public void Init(CookieDto[] cookies, StoreDto[] stores)
|
||||||
|
{
|
||||||
|
Cookies = cookies;
|
||||||
|
Stores = stores;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CookieDto[] Cookies { get; private set; } = null!;
|
||||||
|
|
||||||
|
public StoreDto[] Stores { get; private set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JdTime
|
||||||
|
{
|
||||||
|
public static DateTime ServiceTime { get; set; }
|
||||||
|
}
|
||||||
25
src/Web/Stores/config.json
Normal file
25
src/Web/Stores/config.json
Normal file
File diff suppressed because one or more lines are too long
8
src/Web/Utils/Constants.cs
Normal file
8
src/Web/Utils/Constants.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Web.Utils;
|
||||||
|
|
||||||
|
public static class Constants
|
||||||
|
{
|
||||||
|
public const string JdMobileApiClient = "JdMobileApi";
|
||||||
|
|
||||||
|
public const string H5StClient = "Default";
|
||||||
|
}
|
||||||
25
src/Web/Utils/RandomUtil.cs
Normal file
25
src/Web/Utils/RandomUtil.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
namespace Web.Utils;
|
||||||
|
|
||||||
|
public static class RandomUtil
|
||||||
|
{
|
||||||
|
public static string GetUserAgent()
|
||||||
|
{
|
||||||
|
var random = new Random();
|
||||||
|
return Ua[random.Next(0, Ua.Length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly string[] Ua =
|
||||||
|
[
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Android 7.1.1; Mobile; rv:68.0) Gecko/68.0 Firefox/68.0",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36 OPR/53.0.2569.141117",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36 EdgA/42.0.2.3819",
|
||||||
|
"Mozilla/5.0 (Linux; U; Android 7.1.1; zh-cn; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/9.6 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; U; Android 7.1.1; zh-cn; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 OppoBrowser/10.5.1.2",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.97 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; U; Android 7.1.1; zh-CN; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.6.0.1040 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 LieBaoFast/5.12.3",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/48.0.2564.116 Mobile Safari/537.36 T7/9.1 baidubrowser/7.19.13.0 (Baidu; P1 7.1.1)",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 Mb2345Browser/11.0.1"
|
||||||
|
];
|
||||||
|
}
|
||||||
23
src/Web/Web.csproj
Normal file
23
src/Web/Web.csproj
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Cronos" Version="0.9.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.2" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Update="wwwroot\*">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
15
src/Web/appsettings.Development.json
Normal file
15
src/Web/appsettings.Development.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Interfaces": {
|
||||||
|
"H5StService": {
|
||||||
|
"Url": "http://suncheng.online:27483",
|
||||||
|
"UserName": "suncheng",
|
||||||
|
"Password": "SCsunch940622"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/Web/appsettings.json
Normal file
14
src/Web/appsettings.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Interfaces": {
|
||||||
|
"H5StService": {
|
||||||
|
"Url": "http://jd-h5st:3001"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/Web/bodys.txt
Normal file
38
src/Web/bodys.txt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// 抢购成功
|
||||||
|
|
||||||
|
{
|
||||||
|
"code" : 0,
|
||||||
|
"data" : {
|
||||||
|
"batchId" : 1115819535,
|
||||||
|
"beginTime" : 1733328000000,
|
||||||
|
"couponId" : "386482391638",
|
||||||
|
"discount" : 80.0,
|
||||||
|
"endTime" : 1733673599000,
|
||||||
|
"haveShare" : 0,
|
||||||
|
"quota" : 1800.0,
|
||||||
|
"resultCode" : 999,
|
||||||
|
"resultMsg" : "领取成功!感谢您的参与,祝您购物愉快~"
|
||||||
|
},
|
||||||
|
"message" : "",
|
||||||
|
"subCode" : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已经参加
|
||||||
|
{
|
||||||
|
"code" : 0,
|
||||||
|
"data" : {
|
||||||
|
"batchId" : 0,
|
||||||
|
"haveShare" : 0,
|
||||||
|
"resultCode" : 15,
|
||||||
|
"resultMsg" : "您今天已经参加过此活动,别太贪心哟,明天再来~"
|
||||||
|
},
|
||||||
|
"message" : "",
|
||||||
|
"subCode" : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没抢到
|
||||||
|
{
|
||||||
|
"code":0,
|
||||||
|
"message": "此时排队领券的人太多,看下其他券吧~! ",
|
||||||
|
"subCode":1001
|
||||||
|
}
|
||||||
231
src/Web/cookie.txt
Normal file
231
src/Web/cookie.txt
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
__jdu=1709213308743173682908;
|
||||||
|
|
||||||
|
shshshfpa=6fd919ce-ef6b-4cf5-442b-f6f346022be5-1709213310;
|
||||||
|
|
||||||
|
shshshfpx=6fd919ce-ef6b-4cf5-442b-f6f346022be5-1709213310;
|
||||||
|
|
||||||
|
pinId=zJ-FB-D6TEE8yxkEQLuparV9-x-f3wj7;
|
||||||
|
|
||||||
|
areaId=12;
|
||||||
|
|
||||||
|
PCSYCityID=CN_320000_320500_0;
|
||||||
|
|
||||||
|
TrackID=10r-n2F5xViEUSOnR04BexXSyeOGK3naTAflfsipIG6uHxlfZCcVTTYWZeDMrOkEmkeamb-gjnO-jX8k9UIcbBNTmke2lJUE4_SWgMf59ZEUbWzXmzc-iVozzrPNvWIsr;
|
||||||
|
|
||||||
|
thor=9D682B1019126F2D724E8FD091A5EF32162B4EB323EAB93CACF8A59C3AEAD9FA73F3720539109EBAEC5A7264261AD898EECAFC5D483D744E2AC905E013DCA51F74651F71D794C5B5AA1BB7650B0A78B969D9EE04986E8DC7E08F0E23470FE12ACBDE35171931A2EF9C55244F6C5598709030B078BADB750C6E24A73F192B7CF73C2B326C7BDC0CF8DBADD65CB5D7CE03BA23AFB9F1694265584CAFF9CFF6E061;
|
||||||
|
|
||||||
|
light_key=AASBKE7rOxgWQziEhC_QY6yaboOsu7b0PHOrRox4zrMIAySnyjpARax46M_iFDcLjsUgKiu2;
|
||||||
|
|
||||||
|
pin=jd_6d0087dae2c25;
|
||||||
|
|
||||||
|
unick=jd_141521353;
|
||||||
|
|
||||||
|
_tp=nMhwGCgE1aEoheqAQUkNhfMX89BVigm1i5H5WRHna8M%3D;
|
||||||
|
|
||||||
|
_pst=jd_6d0087dae2c25;
|
||||||
|
|
||||||
|
user-key=0ad4a1d9-e64a-453b-a529-02c0fbabdd12;
|
||||||
|
|
||||||
|
autoOpenApp_downCloseDate_auto=1732959177591_1800000;
|
||||||
|
|
||||||
|
unpl=JF8EALNnNSttUUhWDUtSHRFDS1hTW10LSR9XPWVWXVpdHgYBEwJOFBh7XlVdWBRLFx9tZhRUW1NJVg4eBSsSEXtdU11UC3sRAGZmB1VUUE5kBRwBEhEgSF1kX20ITRYLbGcHVlxRTVEHGwQZFRlIVVVXbQl7FwtoVwVVXVhOUgceAh8aEk5cZG5aDUwRCmZiAWRcaEpkRHUFGBQQShBUWFwASBcBbWYMUlhaS1IHHAsYGhFCbVVuXg;
|
||||||
|
|
||||||
|
warehistory=\"10114734159340,10114734159340,10114734159340,10114734159340,10114734159340,10114734159340,10114734159340,10114734159340,10114734159340,10114734159340,\";
|
||||||
|
|
||||||
|
autoOpenApp_downCloseDate_autoOpenApp_autoPromptly=1732959228129_1;
|
||||||
|
|
||||||
|
__jdv=94967808%7Clianmeng__9__cps.youmai.com%7Ct_16282_728030894%7Cjingfen%7C8239aa72b14641239ec3b864dc591d79%7C1732959228147;
|
||||||
|
|
||||||
|
mt_xid=V2_52007VwMUU1VbUlgdSBBaAGUDFFBaUVFSHkApVVdlAUEHXAtOUxocGUAAYlNGTg0NAFkDHUkIUjMAGwVZWlEPL0oYXwB7AxJOX1lDWhZCGFoOYwMiUG1YYlgZQRlUBmMHF1BaaFJZHEs%3D;
|
||||||
|
|
||||||
|
jcap_dvzw_fp=EEm3fT6O6awEo2MZdqZ6zWKR4WtOPci4YxtLs4LIQ_9qhI-o8kJwl7HTZ1Jj7LK5a6TTZjiRzpvVwhMrv2O3UA==;
|
||||||
|
|
||||||
|
whwswswws=;
|
||||||
|
|
||||||
|
autoOpenApp_downCloseDate_jd_independent_coupon_openapp=1733211527214_1;
|
||||||
|
|
||||||
|
mba_muid=1709213308743173682908;
|
||||||
|
|
||||||
|
retina=1;
|
||||||
|
|
||||||
|
cid=9;
|
||||||
|
|
||||||
|
webp=1;
|
||||||
|
|
||||||
|
visitkey=7582830784651254661;
|
||||||
|
|
||||||
|
sc_width=414;
|
||||||
|
|
||||||
|
equipmentId=4F7K566PLIEHJ6JCQTSBN3R7HRLZ674CRV4RJ3X2S4CTG42J6QNRCT4X2WLNSPVZ6C3FOTLUOVUOAGERKLBMYB6W3U;
|
||||||
|
|
||||||
|
fingerprint=12d463d3ff8c36dc514d71d277f625c7;
|
||||||
|
|
||||||
|
deviceVersion=604.1;
|
||||||
|
|
||||||
|
deviceOS=ios;
|
||||||
|
|
||||||
|
deviceOSVersion=16.6;
|
||||||
|
|
||||||
|
deviceName=Safari;
|
||||||
|
|
||||||
|
TrackerID=WAc5MJ1h-A_0O0LYZ1ERUblkDxA2aCcD-WXn3XrXB24lU7vXBIaQ4xJivUziSovNP_UNpNOc91m9YT5xBTQLzS-DKhKcDb0qdCdHrLqBdpR999rZSrWl1bl6mIT8ZnQieTPqG3LtBebYbrRqgY6Rww;
|
||||||
|
|
||||||
|
pt_key=AAJnTwjvADCjgPbemDeePe_5nPlawvkb2owY5ewTkaDWVPObyRFHbVyiuq8j5jiiNWPgGWPyZoE;
|
||||||
|
|
||||||
|
pt_pin=jd_6d0087dae2c25;
|
||||||
|
|
||||||
|
pt_token=00xa2lv9;
|
||||||
|
|
||||||
|
pwdt_id=jd_6d0087dae2c25;
|
||||||
|
|
||||||
|
sfstoken=tk01m930a1bdda8sMSszeDF4MXgx+IjD/IqCwYZm+0++aU5ob6jY0AmLq6I/YxoFyZwaQ+FvLlKGoNpMIQp+osSgfhAN;
|
||||||
|
|
||||||
|
cn=10;
|
||||||
|
|
||||||
|
ipLoc-djd=12-988-40034-58081.4001065249;
|
||||||
|
|
||||||
|
ipLocation=%u6c5f%u82cf;
|
||||||
|
|
||||||
|
3AB9D23F7A4B3C9B=4F7K566PLIEHJ6JCQTSBN3R7HRLZ674CRV4RJ3X2S4CTG42J6QNRCT4X2WLNSPVZ6C3FOTLUOVUOAGERKLBMYB6W3U;
|
||||||
|
|
||||||
|
cartNum=10;
|
||||||
|
|
||||||
|
kplTitleShow=1;
|
||||||
|
|
||||||
|
e_wq_addr=DNU1CJuyENC4DIU3GzPpDzTpCtqnEV8mTJdNTXU1CzO3TXU0HUPNXyV1DtcnHMV1EJYzCyV1DJCzGV8vdJHPCNuvdJczGUYvdJUyCzKvdJU2HOSvdJczGUYvdJHPDOSvdJu1HtHpTJdNTXU1CzO3TXU0HUPNTXU2DzPOTXU5DtCzTXU1CzDLTXU0HJK5TXU3C0PQTXU1CtCmTXU1DuHMTXU3C0PQTXU0HJHMTXU5DUY0TJdNCJO2BtG0DMUyGzC5BtuyCJu=;
|
||||||
|
|
||||||
|
wq_addr=4551928385%7C1_72_2819_0%7C%u5317%u4EAC_%u671D%u9633%u533A_%u4E09%u73AF%u5230%u56DB%u73AF%u4E4B%u95F4_%7C%u5317%u4EAC%u671D%u9633%u533A%u4E09%u73AF%u5230%u56DB%u73AF%u4E4B%u95F4%7C116.444%2C39.9219;
|
||||||
|
|
||||||
|
jdAddrId=1_72_2819_0;
|
||||||
|
|
||||||
|
jdAddrName=%u5317%u4EAC_%u671D%u9633%u533A_%u4E09%u73AF%u5230%u56DB%u73AF%u4E4B%u95F4_;
|
||||||
|
|
||||||
|
commonAddress=4551928385;
|
||||||
|
|
||||||
|
regionAddress=1%2C72%2C2819%2C0;
|
||||||
|
|
||||||
|
mitemAddrId=1_72_2819_0;
|
||||||
|
|
||||||
|
mitemAddrName=%u5317%u4EAC%u671D%u9633%u533A%u4E09%u73AF%u5230%u56DB%u73AF%u4E4B%u95F4;
|
||||||
|
|
||||||
|
flash=3_x7oFJSmkwddMrpaKudB7np2k_f8HI1c7i8TmD-vmy2aMnqEGyZhQB_j5nK3okn3GGRxYBUHc3TKEWMmjHp5E1hNUL-cIiyCrurW_TB0-vL3kE_sVwd-nYnu7ujl_OaEngkkrzFkT210G0NFSdYqYqgzutNmV8zSRTUnb25PR_IXDnvWhzvvu3e**;
|
||||||
|
|
||||||
|
RT=\"z=1&dm=jd.com&si=n459aq77yu&ss=m48i0fff&sl=1&tt=0&nu=d839338b6dedbc6f545971c201379804&cl=5bqn&obo=1&ld=2d0qp&r=995bb8ebcd478c9cd03c2057729821fc&ul=2d0qq&hd=2d0r3\";
|
||||||
|
|
||||||
|
wxa_level=1;
|
||||||
|
|
||||||
|
jxsid=17335356307253755643;
|
||||||
|
|
||||||
|
__jda=23334881.1709213308743173682908.1709213309.1733236091.1733535630.39;
|
||||||
|
|
||||||
|
__jdc=23334881;
|
||||||
|
|
||||||
|
cd_eid=jdd034F7K566PLIEHJ6JCQTSBN3R7HRLZ674CRV4RJ3X2S4CTG42J6QNRCT4X2WLNSPVZ6C3FOTLUOVUOAGERKLBMYB6W3UAAAAMTRTV6Z4AAAAAADD77R2WGNBL4UQX;
|
||||||
|
|
||||||
|
3AB9D23F7A4B3CSS=jdd034F7K566PLIEHJ6JCQTSBN3R7HRLZ674CRV4RJ3X2S4CTG42J6QNRCT4X2WLNSPVZ6C3FOTLUOVUOAGERKLBMYB6W3UAAAAMTT3DITDYAAAAADFOAFVRLZE33BUX;
|
||||||
|
|
||||||
|
_gia_d=1;
|
||||||
|
|
||||||
|
autoOpenApp_downCloseDate_jd_homePage=1733535631078_1;
|
||||||
|
|
||||||
|
appCode=ms0ca95114;
|
||||||
|
|
||||||
|
mba_sid=17335356307383218012726934471.3;
|
||||||
|
|
||||||
|
__wga=1733535656553.1733535656553.1733236091722.1733232870573.1.3;
|
||||||
|
|
||||||
|
PPRD_P=UUID.1709213308743173682908;
|
||||||
|
|
||||||
|
share_cpin=;
|
||||||
|
|
||||||
|
share_open_id=;
|
||||||
|
|
||||||
|
share_gpin=;
|
||||||
|
|
||||||
|
shareChannel=;
|
||||||
|
|
||||||
|
source_module=;
|
||||||
|
|
||||||
|
erp=;
|
||||||
|
|
||||||
|
jxsid_s_t=1733535656577;
|
||||||
|
|
||||||
|
jxsid_s_u=https%3A//my.m.jd.com/;
|
||||||
|
|
||||||
|
shshshfpb=BApXST3nOnfZAL7feHSyiQfbin4T9xJo_BktgND9-9xJ1ItZfQtDSwkqz2y_7NtRwIeBEUCGCsg;
|
||||||
|
|
||||||
|
__jd_ref_cls=W_jdgwxcx_MyJD_orderSelect;
|
||||||
|
|
||||||
|
wqmnx1=MDEyNjM2MXQvLmNkZHRzc2F4NzYzM28wMWU9dDUxY255ZG9lMTI0NHo1UCBQTzYgU3BiMDVNa2tyMW8xIGkxMTAyWWEtNDFSUyMhKQ%3D%3D;
|
||||||
|
|
||||||
|
__jdb=23334881.4.1709213308743173682908|39.1733535630
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
_gia_d=1;
|
||||||
|
|
||||||
|
__jdb=122270672.3.17331957831321736669308|1.1733195783;
|
||||||
|
|
||||||
|
mba_sid=17331957831337422035107284255.4;
|
||||||
|
|
||||||
|
wqmnx1=MDEyNjM2MnRtbzAxNE1hKGUgZTdsYVhsaS4oLCApaS5vMSBpMUYybi0zUVVPKiZI;
|
||||||
|
|
||||||
|
cd_eid=jdd03QLAOQ67L3E7KBN7R6GYSQRWNO6U5CLCF6EAGSM6KLPLMURRBARVI5CEJAWSTGATVYMLHMTCORINFRBHLGCKZCCP34AAAAAMTRKCN5NQAAAAACPKMZRNGOF5S2QX;
|
||||||
|
|
||||||
|
jxsid=17331957830637784125;
|
||||||
|
|
||||||
|
wxa_level=1;
|
||||||
|
|
||||||
|
sfstoken=tk01m7d111b5fa8sM3gxeDJ4M1A5aw6+5dYWZO6pM+wgqeX3KSXlR2nGFl4TKtaS8Ay3oaMNTtWHHvYgwbA62XuCcGEe;
|
||||||
|
|
||||||
|
__jdv=122270672%7Cdirect%7C-%7Cnone%7C-%7C1733195783133;
|
||||||
|
|
||||||
|
cid=9;
|
||||||
|
|
||||||
|
retina=1;
|
||||||
|
|
||||||
|
__jda=122270672.17331957831321736669308.1733195783.1733195783.1733195783.1;
|
||||||
|
|
||||||
|
mba_muid=17331957831321736669308;
|
||||||
|
|
||||||
|
3AB9D23F7A4B3C9B=QLAOQ67L3E7KBN7R6GYSQRWNO6U5CLCF6EAGSM6KLPLMURRBARVI5CEJAWSTGATVYMLHMTCORINFRBHLGCKZCCP34A;
|
||||||
|
|
||||||
|
3AB9D23F7A4B3CSS=jdd03QLAOQ67L3E7KBN7R6GYSQRWNO6U5CLCF6EAGSM6KLPLMURRBARVI5CEJAWSTGATVYMLHMTCORINFRBHLGCKZCCP34AAAAAMTRKCN5NQAAAAACPKMZRNGOF5S2QX;
|
||||||
|
|
||||||
|
pt_key=AAJnTngiADC9gRJIni4QlYu5AhTp_yblJ0zzsWhvL9Fs-C18ewqvsv90AWt3bG1aEBdq2f5iJ7M;
|
||||||
|
|
||||||
|
pt_pin=jd_ipVkJufWWBjn;
|
||||||
|
|
||||||
|
pt_token=ls6t9uox;
|
||||||
|
|
||||||
|
pwdt_id=jd_ipVkJufWWBjn;
|
||||||
|
|
||||||
|
whwswswws=;
|
||||||
|
|
||||||
|
visitkey=8045458191883437915;
|
||||||
|
|
||||||
|
webp=1;
|
||||||
|
|
||||||
|
shshshfpx=0113eee0-e0a4-d9a1-911c-99306b9eadbe-1733195788;
|
||||||
|
|
||||||
|
shshshfpa=0113eee0-e0a4-d9a1-911c-99306b9eadbe-1733195788;
|
||||||
|
|
||||||
|
shshshfpb=BApXSfp-NifZA5UDwEuM3FSPV8EfQWBxkBnFYUKdo9xJ1Mq5DU4G2;
|
||||||
|
|
||||||
|
jcap_dvzw_fp=lyjmfVqUtx8NoyHMAbwdq_SStYg0E8KDF8QgHOn9DdUMxDpNeukWgWVNUEXi59IH2tOZtyxThZstl9AiAMx0Sw==;
|
||||||
|
|
||||||
|
TrackerID=3fmCY7KTwSPR8pBoEZsL3jAUQj6uN85TuSopYjbkV6UYVHl8HjznPi4tNDLYA0tzjfgx8iW8m4leiRjSiIEmI6q8yJAAK6fV_KKA4KVC3UDs3nCecsqxwsiL2B8D4QXS4Vn3KODr9gZogbbf0gzL_g;
|
||||||
|
|
||||||
|
appCode=ms0ca95114;
|
||||||
|
|
||||||
|
__jdc=122270672;
|
||||||
|
|
||||||
|
autoOpenApp_downCloseDate_jd_homePage=1733195811084_1;
|
||||||
|
|
||||||
|
__jd_ref_cls=MDownLoadFloat_OpenAppSchema;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user