Full Code of laowenruo/Spring-Blog for AI

master 54d4ce78ee9a cached
527 files
4.6 MB
1.3M tokens
1987 symbols
1 requests
Download .txt
Showing preview only (5,051K chars total). Download the full file or copy to clipboard to get everything.
Repository: laowenruo/Spring-Blog
Branch: master
Commit: 54d4ce78ee9a
Files: 527
Total size: 4.6 MB

Directory structure:
gitextract_r_we8a7l/

├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── feature_request.md
│   └── dependabot.yml
├── .gitignore
├── Dockerfile
├── LICENSE.txt
├── README.md
├── README_CN.md
├── blog.sql
├── docker-compose.yml
├── nginx.conf
├── pom.xml
└── src/
    ├── main/
    │   ├── java/
    │   │   └── com/
    │   │       └── blog/
    │   │           ├── BlogApplication.java
    │   │           ├── aspect/
    │   │           │   └── LogAspect.java
    │   │           ├── config/
    │   │           │   ├── RedisConfig.java
    │   │           │   ├── RedisKey.java
    │   │           │   ├── SettingsConfig.java
    │   │           │   └── WebMvcConfig.java
    │   │           ├── controller/
    │   │           │   ├── SitemapController.java
    │   │           │   ├── admin/
    │   │           │   │   ├── AIController.java
    │   │           │   │   ├── AdminController.java
    │   │           │   │   ├── BlogController.java
    │   │           │   │   ├── FriendLinkController.java
    │   │           │   │   ├── SettingsController.java
    │   │           │   │   ├── TagController.java
    │   │           │   │   └── TypeController.java
    │   │           │   ├── blog/
    │   │           │   │   ├── AboutShowController.java
    │   │           │   │   ├── ArchiveShowController.java
    │   │           │   │   ├── FriendLinkControllerShow.java
    │   │           │   │   ├── IndexController.java
    │   │           │   │   ├── MessageController.java
    │   │           │   │   ├── TagShowController.java
    │   │           │   │   └── TypeShowController.java
    │   │           │   └── common/
    │   │           │       └── ControllerExceptionHandler.java
    │   │           ├── dao/
    │   │           │   ├── BlogDao.java
    │   │           │   ├── FriendLinkDao.java
    │   │           │   ├── MessageDao.java
    │   │           │   ├── TagDao.java
    │   │           │   ├── TypeDao.java
    │   │           │   └── UserDao.java
    │   │           ├── entity/
    │   │           │   ├── Blog.java
    │   │           │   ├── BlogAndTag.java
    │   │           │   ├── FriendLink.java
    │   │           │   ├── Message.java
    │   │           │   ├── Tag.java
    │   │           │   ├── Type.java
    │   │           │   └── User.java
    │   │           ├── enums/
    │   │           │   └── BlogStatus.java
    │   │           ├── exception/
    │   │           │   ├── BusinessException.java
    │   │           │   ├── GlobalExceptionHandler.java
    │   │           │   └── NotFoundException.java
    │   │           ├── interceptor/
    │   │           │   └── LoginInterceptor.java
    │   │           ├── pojo/
    │   │           │   ├── RequestLog.java
    │   │           │   └── WebhookMessage.java
    │   │           ├── scheduled/
    │   │           │   └── Refresh.java
    │   │           ├── service/
    │   │           │   ├── AIService.java
    │   │           │   ├── BlogService.java
    │   │           │   ├── FriendLinkService.java
    │   │           │   ├── MessageService.java
    │   │           │   ├── RedisService.java
    │   │           │   ├── SitemapService.java
    │   │           │   ├── SmartSearchService.java
    │   │           │   ├── TagService.java
    │   │           │   ├── TypeService.java
    │   │           │   ├── UserService.java
    │   │           │   └── impl/
    │   │           │       ├── BlogServiceImpl.java
    │   │           │       ├── FriendLinkServiceImpl.java
    │   │           │       ├── MessageServiceImpl.java
    │   │           │       ├── RedisServiceImpl.java
    │   │           │       ├── TagServiceImpl.java
    │   │           │       ├── TypeServiceImpl.java
    │   │           │       └── UserServiceImpl.java
    │   │           └── util/
    │   │               ├── CommonResult.java
    │   │               ├── MD5Utils.java
    │   │               ├── MarkdownUtils.java
    │   │               ├── PasswordUtils.java
    │   │               ├── PropertiesUtil.java
    │   │               ├── RedisUtil.java
    │   │               ├── SEOUtils.java
    │   │               └── WxChatbotClient.java
    │   └── resources/
    │       ├── application-dev.yml
    │       ├── application-pro.yml
    │       ├── application.yml
    │       ├── mapper/
    │       │   ├── BlogDao.xml
    │       │   ├── FriendLinkDao.xml
    │       │   ├── MessageDao.xml
    │       │   ├── TagDao.xml
    │       │   ├── TypeDao.xml
    │       │   └── UserDao.xml
    │       ├── messages.properties
    │       ├── static/
    │       │   ├── backend/
    │       │   │   ├── css/
    │       │   │   │   └── style.css
    │       │   │   ├── icons/
    │       │   │   │   ├── avasta/
    │       │   │   │   │   └── css/
    │       │   │   │   │       └── style.css
    │       │   │   │   ├── bootstrap-icons/
    │       │   │   │   │   └── font/
    │       │   │   │   │       └── bootstrap-icons.css
    │       │   │   │   ├── flaticon/
    │       │   │   │   │   └── flaticon.css
    │       │   │   │   ├── flaticon_1/
    │       │   │   │   │   └── flaticon_1.css
    │       │   │   │   ├── icomoon/
    │       │   │   │   │   └── icomoon.css
    │       │   │   │   ├── simple-line-icons/
    │       │   │   │   │   └── css/
    │       │   │   │   │       └── simple-line-icons.css
    │       │   │   │   └── themify-icons/
    │       │   │   │       └── css/
    │       │   │   │           └── themify-icons.css
    │       │   │   ├── js/
    │       │   │   │   ├── dashboard/
    │       │   │   │   │   ├── coin-details.js
    │       │   │   │   │   ├── dashboard-1.js
    │       │   │   │   │   ├── market-capital.js
    │       │   │   │   │   ├── my-wallet.js
    │       │   │   │   │   └── portofolio.js
    │       │   │   │   ├── demo.js
    │       │   │   │   ├── deznav-init.js
    │       │   │   │   ├── fullcalendar-init.js
    │       │   │   │   ├── plugins-init/
    │       │   │   │   │   ├── bs-daterange-picker-init.js
    │       │   │   │   │   ├── chartist-init.js
    │       │   │   │   │   ├── chartjs-init.js
    │       │   │   │   │   ├── clock-picker-init.js
    │       │   │   │   │   ├── datatables.init.js
    │       │   │   │   │   ├── flot-init.js
    │       │   │   │   │   ├── fullcalendar-init.js
    │       │   │   │   │   ├── jquery-asColorPicker.init.js
    │       │   │   │   │   ├── jquery.validate-init.js
    │       │   │   │   │   ├── jqvmap-init.js
    │       │   │   │   │   ├── material-date-picker-init.js
    │       │   │   │   │   ├── morris-init.js
    │       │   │   │   │   ├── nestable-init.js
    │       │   │   │   │   ├── nouislider-init.js
    │       │   │   │   │   ├── pickadate-init.js
    │       │   │   │   │   ├── piety-init.js
    │       │   │   │   │   ├── select2-init.js
    │       │   │   │   │   ├── sparkline-init.js
    │       │   │   │   │   ├── sweetalert.init.js
    │       │   │   │   │   ├── toastr-init.js
    │       │   │   │   │   └── widgets-script-init.js
    │       │   │   │   └── styleSwitcher.js
    │       │   │   └── vendor/
    │       │   │       ├── jquery-nice-select/
    │       │   │       │   └── css/
    │       │   │       │       └── nice-select.css
    │       │   │       ├── owl-carousel/
    │       │   │       │   ├── owl.carousel.css
    │       │   │       │   └── owl.carousel.js
    │       │   │       └── perfect-scrollbar/
    │       │   │           └── css/
    │       │   │               └── perfect-scrollbar.css
    │       │   ├── css/
    │       │   │   ├── animate.css
    │       │   │   ├── dark-mode.css
    │       │   │   ├── donate.css
    │       │   │   ├── font.css
    │       │   │   ├── foreBlog.css
    │       │   │   ├── friend.css
    │       │   │   ├── themes/
    │       │   │   │   └── default/
    │       │   │   │       └── assets/
    │       │   │   │           └── fonts/
    │       │   │   │               └── icons.otf
    │       │   │   ├── timeline.css
    │       │   │   └── typo.css
    │       │   ├── js/
    │       │   │   ├── article.js
    │       │   │   ├── canvas-ribbon.js
    │       │   │   ├── category.js
    │       │   │   ├── error.js
    │       │   │   ├── foreBlog.js
    │       │   │   ├── home.js
    │       │   │   ├── jquery.js
    │       │   │   ├── tags.js
    │       │   │   ├── theme.js
    │       │   │   └── whatwg-fetch@2.0.3_fetch.js
    │       │   └── lib/
    │       │       ├── editormd/
    │       │       │   ├── css/
    │       │       │   │   ├── editormd.css
    │       │       │   │   ├── editormd.logo.css
    │       │       │   │   └── editormd.preview.css
    │       │       │   ├── editormd.js
    │       │       │   ├── fonts/
    │       │       │   │   └── FontAwesome.otf
    │       │       │   ├── languages/
    │       │       │   │   ├── en.js
    │       │       │   │   └── zh-tw.js
    │       │       │   ├── lib/
    │       │       │   │   └── codemirror/
    │       │       │   │       ├── AUTHORS
    │       │       │   │       ├── LICENSE
    │       │       │   │       ├── README.md
    │       │       │   │       ├── addon/
    │       │       │   │       │   ├── comment/
    │       │       │   │       │   │   ├── comment.js
    │       │       │   │       │   │   └── continuecomment.js
    │       │       │   │       │   ├── dialog/
    │       │       │   │       │   │   ├── dialog.css
    │       │       │   │       │   │   └── dialog.js
    │       │       │   │       │   ├── display/
    │       │       │   │       │   │   ├── fullscreen.css
    │       │       │   │       │   │   ├── fullscreen.js
    │       │       │   │       │   │   ├── panel.js
    │       │       │   │       │   │   ├── placeholder.js
    │       │       │   │       │   │   └── rulers.js
    │       │       │   │       │   ├── edit/
    │       │       │   │       │   │   ├── closebrackets.js
    │       │       │   │       │   │   ├── closetag.js
    │       │       │   │       │   │   ├── continuelist.js
    │       │       │   │       │   │   ├── matchbrackets.js
    │       │       │   │       │   │   ├── matchtags.js
    │       │       │   │       │   │   └── trailingspace.js
    │       │       │   │       │   ├── fold/
    │       │       │   │       │   │   ├── brace-fold.js
    │       │       │   │       │   │   ├── comment-fold.js
    │       │       │   │       │   │   ├── foldcode.js
    │       │       │   │       │   │   ├── foldgutter.css
    │       │       │   │       │   │   ├── foldgutter.js
    │       │       │   │       │   │   ├── indent-fold.js
    │       │       │   │       │   │   ├── markdown-fold.js
    │       │       │   │       │   │   └── xml-fold.js
    │       │       │   │       │   ├── hint/
    │       │       │   │       │   │   ├── anyword-hint.js
    │       │       │   │       │   │   ├── css-hint.js
    │       │       │   │       │   │   ├── html-hint.js
    │       │       │   │       │   │   ├── javascript-hint.js
    │       │       │   │       │   │   ├── show-hint.css
    │       │       │   │       │   │   ├── show-hint.js
    │       │       │   │       │   │   ├── sql-hint.js
    │       │       │   │       │   │   └── xml-hint.js
    │       │       │   │       │   ├── lint/
    │       │       │   │       │   │   ├── coffeescript-lint.js
    │       │       │   │       │   │   ├── css-lint.js
    │       │       │   │       │   │   ├── javascript-lint.js
    │       │       │   │       │   │   ├── json-lint.js
    │       │       │   │       │   │   ├── lint.css
    │       │       │   │       │   │   ├── lint.js
    │       │       │   │       │   │   └── yaml-lint.js
    │       │       │   │       │   ├── merge/
    │       │       │   │       │   │   ├── merge.css
    │       │       │   │       │   │   └── merge.js
    │       │       │   │       │   ├── mode/
    │       │       │   │       │   │   ├── loadmode.js
    │       │       │   │       │   │   ├── multiplex.js
    │       │       │   │       │   │   ├── multiplex_test.js
    │       │       │   │       │   │   ├── overlay.js
    │       │       │   │       │   │   └── simple.js
    │       │       │   │       │   ├── runmode/
    │       │       │   │       │   │   ├── colorize.js
    │       │       │   │       │   │   ├── runmode-standalone.js
    │       │       │   │       │   │   ├── runmode.js
    │       │       │   │       │   │   └── runmode.node.js
    │       │       │   │       │   ├── scroll/
    │       │       │   │       │   │   ├── annotatescrollbar.js
    │       │       │   │       │   │   ├── scrollpastend.js
    │       │       │   │       │   │   ├── simplescrollbars.css
    │       │       │   │       │   │   └── simplescrollbars.js
    │       │       │   │       │   ├── search/
    │       │       │   │       │   │   ├── match-highlighter.js
    │       │       │   │       │   │   ├── matchesonscrollbar.css
    │       │       │   │       │   │   ├── matchesonscrollbar.js
    │       │       │   │       │   │   ├── search.js
    │       │       │   │       │   │   └── searchcursor.js
    │       │       │   │       │   ├── selection/
    │       │       │   │       │   │   ├── active-line.js
    │       │       │   │       │   │   ├── mark-selection.js
    │       │       │   │       │   │   └── selection-pointer.js
    │       │       │   │       │   ├── tern/
    │       │       │   │       │   │   ├── tern.css
    │       │       │   │       │   │   ├── tern.js
    │       │       │   │       │   │   └── worker.js
    │       │       │   │       │   └── wrap/
    │       │       │   │       │       └── hardwrap.js
    │       │       │   │       ├── bower.json
    │       │       │   │       ├── lib/
    │       │       │   │       │   ├── codemirror.css
    │       │       │   │       │   └── codemirror.js
    │       │       │   │       ├── mode/
    │       │       │   │       │   ├── apl/
    │       │       │   │       │   │   ├── apl.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── asterisk/
    │       │       │   │       │   │   ├── asterisk.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── clike/
    │       │       │   │       │   │   ├── clike.js
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── scala.html
    │       │       │   │       │   ├── clojure/
    │       │       │   │       │   │   ├── clojure.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── cobol/
    │       │       │   │       │   │   ├── cobol.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── coffeescript/
    │       │       │   │       │   │   ├── coffeescript.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── commonlisp/
    │       │       │   │       │   │   ├── commonlisp.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── css/
    │       │       │   │       │   │   ├── css.js
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── less.html
    │       │       │   │       │   │   ├── less_test.js
    │       │       │   │       │   │   ├── scss.html
    │       │       │   │       │   │   ├── scss_test.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── cypher/
    │       │       │   │       │   │   ├── cypher.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── d/
    │       │       │   │       │   │   ├── d.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── dart/
    │       │       │   │       │   │   ├── dart.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── diff/
    │       │       │   │       │   │   ├── diff.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── django/
    │       │       │   │       │   │   ├── django.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── dockerfile/
    │       │       │   │       │   │   ├── dockerfile.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── dtd/
    │       │       │   │       │   │   ├── dtd.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── dylan/
    │       │       │   │       │   │   ├── dylan.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── ebnf/
    │       │       │   │       │   │   ├── ebnf.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── ecl/
    │       │       │   │       │   │   ├── ecl.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── eiffel/
    │       │       │   │       │   │   ├── eiffel.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── erlang/
    │       │       │   │       │   │   ├── erlang.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── forth/
    │       │       │   │       │   │   ├── forth.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── fortran/
    │       │       │   │       │   │   ├── fortran.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── gas/
    │       │       │   │       │   │   ├── gas.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── gfm/
    │       │       │   │       │   │   ├── gfm.js
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── gherkin/
    │       │       │   │       │   │   ├── gherkin.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── go/
    │       │       │   │       │   │   ├── go.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── groovy/
    │       │       │   │       │   │   ├── groovy.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── haml/
    │       │       │   │       │   │   ├── haml.js
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── haskell/
    │       │       │   │       │   │   ├── haskell.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── haxe/
    │       │       │   │       │   │   ├── haxe.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── htmlembedded/
    │       │       │   │       │   │   ├── htmlembedded.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── htmlmixed/
    │       │       │   │       │   │   ├── htmlmixed.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── http/
    │       │       │   │       │   │   ├── http.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── idl/
    │       │       │   │       │   │   ├── idl.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── index.html
    │       │       │   │       │   ├── jade/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── jade.js
    │       │       │   │       │   ├── javascript/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── javascript.js
    │       │       │   │       │   │   ├── json-ld.html
    │       │       │   │       │   │   ├── test.js
    │       │       │   │       │   │   └── typescript.html
    │       │       │   │       │   ├── jinja2/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── jinja2.js
    │       │       │   │       │   ├── julia/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── julia.js
    │       │       │   │       │   ├── kotlin/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── kotlin.js
    │       │       │   │       │   ├── livescript/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── livescript.js
    │       │       │   │       │   ├── lua/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── lua.js
    │       │       │   │       │   ├── markdown/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── markdown.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── meta.js
    │       │       │   │       │   ├── mirc/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── mirc.js
    │       │       │   │       │   ├── mllike/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── mllike.js
    │       │       │   │       │   ├── modelica/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── modelica.js
    │       │       │   │       │   ├── nginx/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── nginx.js
    │       │       │   │       │   ├── ntriples/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── ntriples.js
    │       │       │   │       │   ├── octave/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── octave.js
    │       │       │   │       │   ├── pascal/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── pascal.js
    │       │       │   │       │   ├── pegjs/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── pegjs.js
    │       │       │   │       │   ├── perl/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── perl.js
    │       │       │   │       │   ├── php/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── php.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── pig/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── pig.js
    │       │       │   │       │   ├── properties/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── properties.js
    │       │       │   │       │   ├── puppet/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── puppet.js
    │       │       │   │       │   ├── python/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── python.js
    │       │       │   │       │   ├── q/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── q.js
    │       │       │   │       │   ├── r/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── r.js
    │       │       │   │       │   ├── rpm/
    │       │       │   │       │   │   ├── changes/
    │       │       │   │       │   │   │   └── index.html
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── rpm.js
    │       │       │   │       │   ├── rst/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── rst.js
    │       │       │   │       │   ├── ruby/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── ruby.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── rust/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── rust.js
    │       │       │   │       │   ├── sass/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── sass.js
    │       │       │   │       │   ├── scheme/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── scheme.js
    │       │       │   │       │   ├── shell/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── shell.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── sieve/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── sieve.js
    │       │       │   │       │   ├── slim/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── slim.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── smalltalk/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── smalltalk.js
    │       │       │   │       │   ├── smarty/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── smarty.js
    │       │       │   │       │   ├── smartymixed/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── smartymixed.js
    │       │       │   │       │   ├── solr/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── solr.js
    │       │       │   │       │   ├── soy/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── soy.js
    │       │       │   │       │   ├── sparql/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── sparql.js
    │       │       │   │       │   ├── spreadsheet/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── spreadsheet.js
    │       │       │   │       │   ├── sql/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── sql.js
    │       │       │   │       │   ├── stex/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── stex.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── stylus/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── stylus.js
    │       │       │   │       │   ├── tcl/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── tcl.js
    │       │       │   │       │   ├── textile/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── test.js
    │       │       │   │       │   │   └── textile.js
    │       │       │   │       │   ├── tiddlywiki/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── tiddlywiki.css
    │       │       │   │       │   │   └── tiddlywiki.js
    │       │       │   │       │   ├── tiki/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── tiki.css
    │       │       │   │       │   │   └── tiki.js
    │       │       │   │       │   ├── toml/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── toml.js
    │       │       │   │       │   ├── tornado/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── tornado.js
    │       │       │   │       │   ├── turtle/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── turtle.js
    │       │       │   │       │   ├── vb/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── vb.js
    │       │       │   │       │   ├── vbscript/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── vbscript.js
    │       │       │   │       │   ├── velocity/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── velocity.js
    │       │       │   │       │   ├── verilog/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── test.js
    │       │       │   │       │   │   └── verilog.js
    │       │       │   │       │   ├── xml/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── test.js
    │       │       │   │       │   │   └── xml.js
    │       │       │   │       │   ├── xquery/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── test.js
    │       │       │   │       │   │   └── xquery.js
    │       │       │   │       │   ├── yaml/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── yaml.js
    │       │       │   │       │   └── z80/
    │       │       │   │       │       ├── index.html
    │       │       │   │       │       └── z80.js
    │       │       │   │       └── theme/
    │       │       │   │           ├── 3024-day.css
    │       │       │   │           ├── 3024-night.css
    │       │       │   │           ├── ambiance-mobile.css
    │       │       │   │           ├── ambiance.css
    │       │       │   │           ├── base16-dark.css
    │       │       │   │           ├── base16-light.css
    │       │       │   │           ├── blackboard.css
    │       │       │   │           ├── cobalt.css
    │       │       │   │           ├── colorforth.css
    │       │       │   │           ├── eclipse.css
    │       │       │   │           ├── elegant.css
    │       │       │   │           ├── erlang-dark.css
    │       │       │   │           ├── lesser-dark.css
    │       │       │   │           ├── mbo.css
    │       │       │   │           ├── mdn-like.css
    │       │       │   │           ├── midnight.css
    │       │       │   │           ├── monokai.css
    │       │       │   │           ├── neat.css
    │       │       │   │           ├── neo.css
    │       │       │   │           ├── night.css
    │       │       │   │           ├── paraiso-dark.css
    │       │       │   │           ├── paraiso-light.css
    │       │       │   │           ├── pastel-on-dark.css
    │       │       │   │           ├── rubyblue.css
    │       │       │   │           ├── solarized.css
    │       │       │   │           ├── the-matrix.css
    │       │       │   │           ├── tomorrow-night-bright.css
    │       │       │   │           ├── tomorrow-night-eighties.css
    │       │       │   │           ├── twilight.css
    │       │       │   │           ├── vibrant-ink.css
    │       │       │   │           ├── xq-dark.css
    │       │       │   │           ├── xq-light.css
    │       │       │   │           └── zenburn.css
    │       │       │   └── plugins/
    │       │       │       ├── code-block-dialog/
    │       │       │       │   └── code-block-dialog.js
    │       │       │       ├── emoji-dialog/
    │       │       │       │   ├── emoji-dialog.js
    │       │       │       │   └── emoji.json
    │       │       │       ├── goto-line-dialog/
    │       │       │       │   └── goto-line-dialog.js
    │       │       │       ├── help-dialog/
    │       │       │       │   ├── help-dialog.js
    │       │       │       │   └── help.md
    │       │       │       ├── html-entities-dialog/
    │       │       │       │   ├── html-entities-dialog.js
    │       │       │       │   └── html-entities.json
    │       │       │       ├── image-dialog/
    │       │       │       │   └── image-dialog.js
    │       │       │       ├── link-dialog/
    │       │       │       │   └── link-dialog.js
    │       │       │       ├── plugin-template.js
    │       │       │       ├── preformatted-text-dialog/
    │       │       │       │   └── preformatted-text-dialog.js
    │       │       │       ├── reference-link-dialog/
    │       │       │       │   └── reference-link-dialog.js
    │       │       │       ├── table-dialog/
    │       │       │       │   └── table-dialog.js
    │       │       │       └── test-plugin/
    │       │       │           └── test-plugin.js
    │       │       ├── prism/
    │       │       │   ├── prism.css
    │       │       │   └── prism.js
    │       │       └── tocbot/
    │       │           ├── tocbot.css
    │       │           └── tocbot.js
    │       └── templates/
    │           ├── about.html
    │           ├── admin/
    │           │   ├── ai-assistant.html
    │           │   ├── blogs-input.html
    │           │   ├── blogs.html
    │           │   ├── fragments/
    │           │   │   ├── footer.html
    │           │   │   ├── header.html
    │           │   │   └── sidebar.html
    │           │   ├── friendLinks-input.html
    │           │   ├── friendLinks.html
    │           │   ├── index.html
    │           │   ├── login.html
    │           │   ├── settings.html
    │           │   ├── tags-input.html
    │           │   ├── tags.html
    │           │   ├── types-input.html
    │           │   ├── types.html
    │           │   ├── users-input.html
    │           │   └── users.html
    │           ├── blog.html
    │           ├── error/
    │           │   ├── 404.html
    │           │   ├── 500.html
    │           │   └── error.html
    │           ├── fragments/
    │           │   ├── footer.html
    │           │   └── header.html
    │           ├── friends.html
    │           ├── index.html
    │           ├── message.html
    │           ├── search.html
    │           ├── tags.html
    │           ├── time.html
    │           └── types.html
    └── test/
        └── java/
            └── com/
                └── blog/
                    ├── BlogApplicationTests.java
                    ├── service/
                    │   ├── AIServiceTest.java
                    │   └── SitemapServiceTest.java
                    └── util/
                        ├── PasswordUtilsTest.java
                        └── SEOUtilsTest.java

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
*.js linguist-language=Java
*.css linguist-language=Java
*.html linguist-language=Java


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# create
version: 2
updates:
  - package-ecosystem: "" # See documentation for possible values
    directory: "/" # Location of package manifests
    schedule:
      interval: "weekly"


================================================
FILE: .gitignore
================================================
/target/
/.idea/
/blog-dev.log
/log/

# Node.js dependencies in static resources (security risk)
src/main/resources/static/**/package.json
src/main/resources/static/**/package-lock.json
src/main/resources/static/**/node_modules/

================================================
FILE: Dockerfile
================================================
# SpringBoot AI Blog - Docker 镜像
FROM openjdk:11-jre-slim

# 作者信息
LABEL maintainer="tangredtea <tangredtea@gmail.com>"
LABEL description="AI驱动的智能博客系统 - Spring Boot + MyBatis + AI集成"

# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 创建工作目录
WORKDIR /app

# 将可执行的 jar 包放到容器中
COPY target/blog-1.0.0.jar app.jar

# 暴露服务端口
EXPOSE 8080

# 创建日志目录
RUN mkdir -p /logs
VOLUME ["/logs"]

# 环境变量配置
ENV JAVA_OPTS="-Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai"
ENV JVM_OPTS="-Xmx512m -Xms512m -Xmn256m -XX:+UseG1GC"
ENV APP_OPTS=""

# AI 配置(可选)
ENV AI_API_KEY=""
ENV AI_API_URL="https://api.openai.com/v1/chat/completions"
ENV AI_MODEL="gpt-3.5-turbo"

# 数据库配置(运行时通过环境变量传入)
ENV DB_HOST="mysql"
ENV DB_PORT="3306"
ENV DB_NAME="blog"
ENV DB_USERNAME="root"
ENV DB_PASSWORD=""
ENV REDIS_HOST="redis"
ENV REDIS_PORT="6379"
ENV REDIS_PASSWORD=""

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
  CMD curl -f http://localhost:8080/actuator/health || exit 1

# 运行程序
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS $JVM_OPTS $APP_OPTS -jar app.jar"]


================================================
FILE: LICENSE.txt
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# SpringBoot AI Blog

[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![Stars](https://img.shields.io/github/stars/tangredtea/Spring-Blog?style=social)](https://github.com/tangredtea/Spring-Blog/stargazers)
[![Issues](https://img.shields.io/github/issues/tangredtea/Spring-Blog)](https://github.com/tangredtea/Spring-Blog/issues)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)

> A personal blog system built with Spring Boot + MyBatis, featuring AI-powered content assistance, Redis caching, and a clean admin dashboard.

English | [简体中文](README_CN.md)

---

## Features

### AI Integration
- **Smart Summary** - Auto-generate article summaries via AI
- **Tag Suggestions** - AI-recommended tags based on content
- **Article Scoring** - Quality assessment with improvement suggestions
- **Smart Search** - Keyword extraction and related article recommendations

### Content Management
- **Rich Editor** - Markdown editor with live preview
- **Draft System** - Save unpublished articles as drafts
- **Category & Tags** - Flexible content organization
- **Friend Links** - Blogroll management
- **Comment System** - Valine-based serverless comments
- **Message Board** - Visitor guestbook

### Admin Dashboard
- **Statistics Overview** - Article count, views, tags, categories at a glance
- **AI Status Monitor** - Check AI service availability from dashboard
- **Quick Actions** - One-click shortcuts for common operations
- **Recent Articles** - Latest published posts table

### Performance
- **Redis Caching** - Accelerated page loading
- **Database Indexing** - Optimized query performance
- **HikariCP** - High-performance connection pool

### Security
- **BCrypt Encryption** - Secure password hashing (Spring Security Crypto)
- **SQL Injection Protection** - MyBatis parameterized queries
- **Login Interceptor** - Admin route protection
- **Input Validation** - Bean Validation (JSR-380)

### Additional Features
- **SEO Optimization** - Sitemap generation, meta tags, structured data
- **Markdown Support** - CommonMark parser with GFM tables and heading anchors
- **Exception Monitoring** - WeChat Work webhook notifications for system errors
- **Global Exception Handling** - Custom error pages with detailed logging
- **AOP Logging** - Request/response logging with aspect-oriented programming
- **Scheduled Tasks** - Automatic cache refresh and maintenance

---

## Tech Stack

| Layer | Technology |
|-------|-----------|
| Framework | Spring Boot 2.7.x |
| ORM | MyBatis |
| Database | MySQL 8.0 |
| Cache | Redis |
| Template Engine | Thymeleaf |
| Pagination | PageHelper |
| Password Encryption | BCrypt |
| Connection Pool | HikariCP |

---

## Quick Start

### Prerequisites
- JDK 8+ (tested with JDK 21)
- MySQL 5.7+ (recommended MySQL 8.0)
- Redis 5.0+
- Maven 3.6+

### Installation

1. **Clone the repository**
```bash
git clone https://github.com/tangredtea/Spring-Blog.git
cd Spring-Blog
```

2. **Initialize the database**
```bash
mysql -u root -p < blog.sql
```

3. **Configure environment variables** (recommended)
```bash
export DB_USERNAME=root
export DB_PASSWORD=your_password
export REDIS_PASSWORD=your_redis_password  # if applicable
export AI_API_KEY=your_openai_api_key      # optional, for AI features
```

4. **Run the application**
```bash
mvn spring-boot:run
```

5. **Access the application**
- Frontend: http://localhost:8080
- Admin panel: http://localhost:8080/admin
- Default credentials: `admin` / `admin123`

### Docker Compose Deployment

```bash
# 1. Copy config
cp .env.example .env

# 2. Edit config
vim .env

# 3. Start all services
docker-compose up -d
```

---

## Project Structure

```
Spring-Blog/
├── src/main/java/com/blog/
│   ├── controller/      # Controllers (admin + frontend + common)
│   │   ├── admin/       # Admin panel controllers
│   │   ├── blog/        # Frontend blog controllers
│   │   ├── common/      # Common controllers
│   │   └── SitemapController.java  # SEO sitemap
│   ├── service/         # Business logic & AI services
│   │   └── impl/        # Service implementations
│   ├── dao/             # Data access layer (MyBatis mappers)
│   ├── entity/          # Entity classes (Blog, User, Tag, etc.)
│   ├── pojo/            # Plain Old Java Objects (DTOs)
│   ├── config/          # Configuration (Redis, WebMvc, Settings)
│   ├── interceptor/     # Login interceptor
│   ├── aspect/          # AOP logging
│   ├── scheduled/       # Scheduled tasks (cache refresh)
│   ├── exception/       # Global exception handling
│   ├── enums/           # Enumerations (BlogStatus, etc.)
│   └── util/            # Utilities (Password, SEO, Markdown, etc.)
├── src/main/resources/
│   ├── mapper/          # MyBatis XML mappers
│   ├── templates/       # Thymeleaf templates
│   │   ├── admin/       # Admin panel pages
│   │   ├── fragments/   # Reusable fragments
│   │   └── error/       # Error pages (404, 500)
│   ├── static/          # Static resources (CSS/JS/images)
│   │   ├── css/         # Stylesheets
│   │   ├── js/          # JavaScript files
│   │   ├── images/      # Images
│   │   ├── fonts/       # Web fonts
│   │   └── lib/         # Third-party libraries
│   ├── application.yml  # Main configuration
│   ├── application-dev.yml   # Development config
│   ├── application-pro.yml   # Production config
│   └── messages.properties   # i18n messages
├── src/test/            # Unit tests
├── blog.sql             # Database schema & seed data
├── Dockerfile           # Docker build
├── docker-compose.yml   # Docker Compose
├── nginx.conf           # Nginx configuration
└── .env.example         # Environment variables template
```

---

## Configuration

### AI Configuration (Optional)

AI features are optional. To enable them, set the following in `application-dev.yml` or via environment variables:

```yaml
ai:
  api:
    key: ${AI_API_KEY:}         # OpenAI API key
    url: ${AI_API_URL:https://api.openai.com/v1/chat/completions}
  model: ${AI_MODEL:gpt-3.5-turbo}
```

When AI is not configured, the system gracefully falls back to default behavior (no errors).

### Site Settings

Edit `src/main/resources/messages.properties` to customize your blog:

```properties
# Basic Info
web_Name=Your Blog Name
web_Description=Your blog description
web_Keywords=Java Blog, Tech Blog

# Social Links
web_Github=https://github.com/yourusername
web_Csdn=https://blog.csdn.net/yourusername

# Comment System (Valine)
valine_AppID=your_leancloud_appid
valine_AppKey=your_leancloud_appkey

# WeChat Work Webhook (Optional - for error notifications)
wx_Webhook=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your_key
# Set to "0" to disable webhook notifications
```

---

## Contributing

Contributions are welcome! Feel free to open issues and pull requests.

1. Fork the repository
2. Create your branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

---

## License

[Apache License 2.0](LICENSE) - tangredtea


================================================
FILE: README_CN.md
================================================
# SpringBoot AI 博客系统

[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![Stars](https://img.shields.io/github/stars/tangredtea/Spring-Blog?style=social)](https://github.com/tangredtea/Spring-Blog/stargazers)
[![Issues](https://img.shields.io/github/issues/tangredtea/Spring-Blog)](https://github.com/tangredtea/Spring-Blog/issues)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)

> 基于 Spring Boot + MyBatis 构建的个人博客系统,集成 AI 智能辅助、Redis 缓存和简洁的后台管理界面。

[English](README.md) | 简体中文

---

## 功能特性

### AI 智能集成
- **智能摘要** - AI 自动生成文章摘要
- **标签推荐** - 基于内容的 AI 标签建议
- **文章评分** - 质量评估与改进建议
- **智能搜索** - 关键词提取和相关文章推荐

### 内容管理
- **富文本编辑器** - Markdown 编辑器,支持实时预览
- **草稿系统** - 保存未发布的文章为草稿
- **分类与标签** - 灵活的内容组织方式
- **友情链接** - 友链管理
- **评论系统** - 基于 Valine 的无服务器评论
- **留言板** - 访客留言功能

### 后台管理
- **统计概览** - 文章数、浏览量、标签、分类一目了然
- **AI 状态监控** - 从仪表板检查 AI 服务可用性
- **快捷操作** - 常用操作的一键快捷方式
- **最新文章** - 最近发布的文章列表

### 性能优化
- **Redis 缓存** - 加速页面加载
- **数据库索引** - 优化查询性能
- **HikariCP** - 高性能连接池

### 安全特性
- **BCrypt 加密** - 安全的密码哈希(Spring Security Crypto)
- **SQL 注入防护** - MyBatis 参数化查询
- **登录拦截器** - 后台路由保护
- **输入验证** - Bean Validation(JSR-380)

### 附加功能
- **SEO 优化** - 站点地图生成、元标签、结构化数据
- **Markdown 支持** - CommonMark 解析器,支持 GFM 表格和标题锚点
- **异常监控** - 企业微信 Webhook 通知系统错误
- **全局异常处理** - 自定义错误页面和详细日志记录
- **AOP 日志** - 基于面向切面编程的请求/响应日志
- **定时任务** - 自动缓存刷新和维护

---

## 技术栈

| 层级 | 技术 |
|-------|-----------|
| 框架 | Spring Boot 2.7.x |
| ORM | MyBatis |
| 数据库 | MySQL 8.0 |
| 缓存 | Redis |
| 模板引擎 | Thymeleaf |
| 分页 | PageHelper |
| 密码加密 | BCrypt |
| 连接池 | HikariCP |

---

## 快速开始

### 环境要求
- JDK 8+(已在 JDK 21 上测试)
- MySQL 5.7+(推荐 MySQL 8.0)
- Redis 5.0+
- Maven 3.6+

### 安装步骤

1. **克隆仓库**
```bash
git clone https://github.com/tangredtea/Spring-Blog.git
cd Spring-Blog
```

2. **初始化数据库**
```bash
mysql -u root -p < blog.sql
```

3. **配置环境变量**(推荐)
```bash
export DB_USERNAME=root
export DB_PASSWORD=your_password
export REDIS_PASSWORD=your_redis_password  # 如果需要
export AI_API_KEY=your_openai_api_key      # 可选,用于 AI 功能
```

4. **运行应用**
```bash
mvn spring-boot:run
```

5. **访问应用**
- 前台:http://localhost:8080
- 后台管理:http://localhost:8080/admin
- 默认账号:`admin` / `admin123`

### Docker Compose 部署

```bash
# 1. 复制配置文件
cp .env.example .env

# 2. 编辑配置
vim .env

# 3. 启动所有服务
docker-compose up -d
```

---

## 项目结构

```
Spring-Blog/
├── src/main/java/com/blog/
│   ├── controller/      # 控制器(后台 + 前台 + 通用)
│   │   ├── admin/       # 后台管理控制器
│   │   ├── blog/        # 前台博客控制器
│   │   ├── common/      # 通用控制器
│   │   └── SitemapController.java  # SEO 站点地图
│   ├── service/         # 业务逻辑 & AI 服务
│   │   └── impl/        # 服务实现类
│   ├── dao/             # 数据访问层(MyBatis 映射器)
│   ├── entity/          # 实体类(Blog、User、Tag 等)
│   ├── pojo/            # 数据传输对象(DTOs)
│   ├── config/          # 配置(Redis、WebMvc、Settings)
│   ├── interceptor/     # 登录拦截器
│   ├── aspect/          # AOP 日志
│   ├── scheduled/       # 定时任务(缓存刷新)
│   ├── exception/       # 全局异常处理
│   ├── enums/           # 枚举类(BlogStatus 等)
│   └── util/            # 工具类(密码、SEO、Markdown 等)
├── src/main/resources/
│   ├── mapper/          # MyBatis XML 映射文件
│   ├── templates/       # Thymeleaf 模板
│   │   ├── admin/       # 后台管理页面
│   │   ├── fragments/   # 可复用片段
│   │   └── error/       # 错误页面(404、500)
│   ├── static/          # 静态资源(CSS/JS/图片)
│   │   ├── css/         # 样式表
│   │   ├── js/          # JavaScript 文件
│   │   ├── images/      # 图片
│   │   ├── fonts/       # Web 字体
│   │   └── lib/         # 第三方库
│   ├── application.yml  # 主配置文件
│   ├── application-dev.yml   # 开发环境配置
│   ├── application-pro.yml   # 生产环境配置
│   └── messages.properties   # 国际化消息
├── src/test/            # 单元测试
├── blog.sql             # 数据库结构 & 初始数据
├── Dockerfile           # Docker 构建
├── docker-compose.yml   # Docker Compose
├── nginx.conf           # Nginx 配置
└── .env.example         # 环境变量模板
```

---

## 配置说明

### AI 配置(可选)

AI 功能是可选的。要启用它们,请在 `application-dev.yml` 中设置或通过环境变量配置:

```yaml
ai:
  api:
    key: ${AI_API_KEY:}         # OpenAI API 密钥
    url: ${AI_API_URL:https://api.openai.com/v1/chat/completions}
  model: ${AI_MODEL:gpt-3.5-turbo}
```

当未配置 AI 时,系统会优雅地回退到默认行为(不会报错)。

### 站点设置

编辑 `src/main/resources/messages.properties` 自定义你的博客:

```properties
# 基本信息
web_Name=你的博客名称
web_Description=你的博客描述
web_Keywords=Java博客, 技术博客

# 社交链接
web_Github=https://github.com/yourusername
web_Csdn=https://blog.csdn.net/yourusername

# 评论系统(Valine)
valine_AppID=your_leancloud_appid
valine_AppKey=your_leancloud_appkey

# 企业微信 Webhook(可选 - 用于错误通知)
wx_Webhook=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your_key
# 设置为 "0" 可禁用 webhook 通知
```

---

## 贡献指南

欢迎贡献!随时提交 issue 和 pull request。

1. Fork 本仓库
2. 创建你的分支 (`git checkout -b feature/amazing-feature`)
3. 提交你的更改 (`git commit -m 'Add amazing feature'`)
4. 推送到分支 (`git push origin feature/amazing-feature`)
5. 开启 Pull Request

---

## 开源协议

[Apache License 2.0](LICENSE) - tangredtea


================================================
FILE: blog.sql
================================================
-- --------------------------------------------------------
-- Spring-Blog 初始化数据
-- 适用于 MySQL 8.x
-- --------------------------------------------------------

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;

CREATE DATABASE IF NOT EXISTS `blog` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `blog`;

-- ----------------------------
-- 用户表
-- ----------------------------
CREATE TABLE IF NOT EXISTS `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户表主键id',
  `username` varchar(255) NOT NULL DEFAULT '' COMMENT '用户名',
  `password` varchar(255) NOT NULL DEFAULT '' COMMENT '用户密码',
  `nickname` varchar(255) DEFAULT NULL COMMENT '用户昵称',
  `email` varchar(255) DEFAULT NULL COMMENT '用户邮箱',
  `avatar` varchar(255) DEFAULT NULL COMMENT '用户头像',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

/*!40000 ALTER TABLE `t_user` DISABLE KEYS */;
REPLACE INTO `t_user` (`id`, `username`, `password`, `nickname`, `email`, `avatar`) VALUES
    (1, 'admin', '$2a$10$obu8GJJJ6CbBr6oS/HIQGeH1E4MsfXiirhC.y0NxiYtMWI5HUoxA6', 'tangredtea', 'tangredtea@gmail.com', 'https://picsum.photos/seed/admin/200/200'),
    (2, 'guest', '$2a$10$b0k6ETX.fw2e2RmtlUSSnORFTkg/2FchBuT12seoGnV0b1iQvyrLK', '访客编辑', 'guest@example.com', 'https://picsum.photos/seed/guest/200/200');
/*!40000 ALTER TABLE `t_user` ENABLE KEYS */;

-- ----------------------------
-- 分类表
-- ----------------------------
CREATE TABLE IF NOT EXISTS `t_type` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '分类主键id',
  `name` varchar(255) NOT NULL COMMENT '分类名',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

/*!40000 ALTER TABLE `t_type` DISABLE KEYS */;
REPLACE INTO `t_type` (`id`, `name`) VALUES
    (1, 'Java后端'),
    (2, 'Spring框架'),
    (3, '数据库'),
    (4, '前端开发'),
    (5, '架构设计'),
    (6, 'DevOps'),
    (7, '读书笔记');
/*!40000 ALTER TABLE `t_type` ENABLE KEYS */;

-- ----------------------------
-- 标签表
-- ----------------------------
CREATE TABLE IF NOT EXISTS `t_tag` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '标签表主键',
  `name` varchar(255) NOT NULL COMMENT '标签名字',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

/*!40000 ALTER TABLE `t_tag` DISABLE KEYS */;
REPLACE INTO `t_tag` (`id`, `name`) VALUES
    (1, 'Java'),
    (2, 'Spring Boot'),
    (3, 'MySQL'),
    (4, 'Redis'),
    (5, 'MyBatis'),
    (6, 'Docker'),
    (7, 'Vue.js'),
    (8, '设计模式'),
    (9, '并发编程'),
    (10, 'JVM'),
    (11, 'Linux'),
    (12, '微服务');
/*!40000 ALTER TABLE `t_tag` ENABLE KEYS */;

-- ----------------------------
-- 博客表
-- ----------------------------
CREATE TABLE IF NOT EXISTS `t_blog` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '博客表主键id',
  `title` varchar(255) DEFAULT NULL COMMENT '博客标题',
  `description` text COMMENT '文章描述',
  `content` mediumtext COMMENT '博文内容',
  `first_picture` varchar(255) DEFAULT NULL COMMENT '博文封面',
  `views` int(11) DEFAULT '0' COMMENT '文章阅读量',
  `flag` bit(1) DEFAULT NULL COMMENT '文章状态位,1:原创,0:转载',
  `appreciation` bit(1) DEFAULT NULL COMMENT '文章状态位,1:开启,0:关闭',
  `share_statement` bit(1) DEFAULT NULL COMMENT '分享状态位,1:开启,0:关闭',
  `commentable` bit(1) DEFAULT NULL COMMENT '评论状态位,1:开启,0:关闭',
  `published` bit(1) DEFAULT NULL COMMENT '发布状态位,1:已发布,0:草稿',
  `recommend` bit(1) DEFAULT NULL COMMENT '推荐状态位,1:开启,0:关闭',
  `is_deleted` bit(1) DEFAULT b'0' COMMENT '删除状态,1:已删除,0:正常',
  `is_top` bit(1) DEFAULT b'0' COMMENT '置顶状态,1:置顶,0:普通',
  `password` varchar(64) DEFAULT NULL COMMENT '文章密码,为空表示公开',
  `create_time` datetime DEFAULT NULL COMMENT '文章创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '文章修改时间',
  `publish_time` datetime DEFAULT NULL COMMENT '文章发布时间',
  `type_id` int(11) DEFAULT NULL COMMENT '关联的分类id',
  `user_id` int(11) DEFAULT NULL COMMENT '关联的用户id',
  `tag_ids` varchar(100) DEFAULT NULL COMMENT '关联标签',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_type_user` (`type_id`,`user_id`) USING BTREE,
  KEY `idx_published` (`published`) USING BTREE,
  KEY `idx_is_deleted` (`is_deleted`) USING BTREE,
  KEY `idx_create_time` (`create_time`) USING BTREE,
  KEY `idx_views` (`views` DESC) USING BTREE,
  KEY `idx_recommend_update` (`recommend`,`update_time` DESC) USING BTREE,
  FULLTEXT KEY `ft_title_content` (`title`,`description`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;

/*!40000 ALTER TABLE `t_blog` DISABLE KEYS */;
REPLACE INTO `t_blog` (`id`, `title`, `description`, `content`, `first_picture`, `views`, `flag`, `appreciation`, `share_statement`, `commentable`, `published`, `recommend`, `is_deleted`, `is_top`, `create_time`, `update_time`, `publish_time`, `type_id`, `user_id`, `tag_ids`) VALUES
    (1, 'Spring Boot 3.x 新特性详解与迁移指南',
     '本文将全面介绍 Spring Boot 3.x 带来的重大变化,包括 Java 17 基线、Jakarta EE 9+ 迁移、GraalVM 原生镜像支持等核心特性,帮助开发者平稳升级。',
     '## Spring Boot 3.x 新特性\n\n### 1. Java 17 基线\n\nSpring Boot 3.x 将最低 Java 版本提升至 17,可以使用 Records、Sealed Classes、Pattern Matching 等新语法特性。\n\n```java\npublic record UserDTO(String name, String email) {}\n```\n\n### 2. Jakarta EE 9+ 迁移\n\n所有 `javax.*` 包名已改为 `jakarta.*`,这是最大的破坏性变更。\n\n```java\n// Before\nimport javax.servlet.http.HttpServletRequest;\n// After\nimport jakarta.servlet.http.HttpServletRequest;\n```\n\n### 3. GraalVM 原生镜像\n\nSpring Boot 3.x 内建对 GraalVM Native Image 的支持,应用启动时间可缩短至毫秒级。\n\n### 4. 可观测性增强\n\n引入 Micrometer Observation API,统一了 Metrics 和 Tracing 的编程模型。\n\n### 总结\n\nSpring Boot 3.x 是一次重大升级,建议在新项目中直接采用,老项目逐步迁移。',
     'https://picsum.photos/seed/springboot3/800/450', 1286, b'1', b'1', b'1', b'1', b'1', b'1', b'0', b'1',
     '2025-11-10 09:30:00', '2026-01-15 14:20:00', '2025-11-10 10:00:00', 2, 1, '1,2'),

    (2, '深入理解 JVM 垃圾回收机制:从 CMS 到 ZGC',
     '详细分析 JVM 中主流垃圾回收器的工作原理,包括 CMS、G1、ZGC 的对比,以及在生产环境中的调优经验。',
     '## JVM 垃圾回收机制\n\n### 垃圾回收算法基础\n\n- **标记-清除**:最基础的 GC 算法\n- **标记-整理**:解决内存碎片问题\n- **复制算法**:新生代常用策略\n\n### CMS 收集器\n\nConcurrent Mark Sweep,以最短停顿时间为目标的收集器,适合对响应时间敏感的应用。\n\n### G1 收集器\n\n将堆内存分割为多个 Region,兼顾吞吐量和停顿时间。JDK 9 起成为默认 GC。\n\n### ZGC\n\n- 停顿时间不超过 10ms\n- 支持 TB 级堆内存\n- JDK 15 开始可用于生产\n\n```bash\njava -XX:+UseZGC -Xmx16g -jar app.jar\n```\n\n### 调优建议\n\n1. 优先选择 G1 或 ZGC\n2. 合理设置堆大小\n3. 关注 GC 日志分析',
     'https://picsum.photos/seed/jvmgc/800/450', 952, b'1', b'1', b'1', b'1', b'1', b'1', b'0', b'0',
     '2025-10-05 15:00:00', '2025-12-20 10:30:00', '2025-10-05 16:00:00', 1, 1, '1,10'),

    (3, 'MySQL 索引优化实战:从慢查询到秒级响应',
     '通过真实案例演示 MySQL 慢查询分析和索引优化过程,涵盖 EXPLAIN 解读、联合索引设计、覆盖索引等核心技巧。',
     '## MySQL 索引优化\n\n### 问题背景\n\n生产环境某查询耗时超过 5 秒,影响用户体验。\n\n### EXPLAIN 分析\n\n```sql\nEXPLAIN SELECT * FROM orders \nWHERE user_id = 1001 AND status = 1 \nORDER BY create_time DESC LIMIT 20;\n```\n\n`type: ALL` 表示全表扫描,需要优化。\n\n### 优化方案\n\n#### 1. 建立联合索引\n\n```sql\nALTER TABLE orders ADD INDEX idx_user_status_time(user_id, status, create_time);\n```\n\n#### 2. 使用覆盖索引\n\n只查询索引中包含的列,避免回表。\n\n#### 3. 分页优化\n\n深分页使用游标方式替代 OFFSET。\n\n### 优化结果\n\n查询时间从 5.2s 降至 12ms,性能提升 400 倍。',
     'https://picsum.photos/seed/mysqlindex/800/450', 738, b'1', b'1', b'1', b'1', b'1', b'0', b'0', b'0',
     '2025-09-18 11:00:00', '2025-11-05 08:45:00', '2025-09-18 12:00:00', 3, 1, '3,1'),

    (4, 'Redis 分布式锁的正确实现方式',
     '对比分析基于 SETNX、Redisson、RedLock 三种 Redis 分布式锁方案的优缺点,给出生产环境推荐实践。',
     '## Redis 分布式锁\n\n### 为什么需要分布式锁?\n\n在微服务架构下,多个实例可能同时操作同一资源,需要分布式锁保证互斥。\n\n### 方案一:SETNX + EXPIRE\n\n```java\nBoolean locked = redisTemplate.opsForValue()\n    .setIfAbsent(\"lock:order:\" + orderId, requestId, 30, TimeUnit.SECONDS);\n```\n\n存在的问题:锁过期但业务未执行完。\n\n### 方案二:Redisson\n\n```java\nRLock lock = redissonClient.getLock(\"lock:order:\" + orderId);\ntry {\n    lock.lock(30, TimeUnit.SECONDS);\n    // 业务逻辑\n} finally {\n    lock.unlock();\n}\n```\n\nRedisson 内置看门狗机制,自动续期。\n\n### 方案三:RedLock\n\n适用于 Redis 集群场景,需要在多数节点加锁成功。\n\n### 推荐\n\n单机/哨兵模式用 Redisson,集群模式考虑 RedLock。',
     'https://picsum.photos/seed/redislock/800/450', 623, b'1', b'1', b'1', b'1', b'1', b'1', b'0', b'0',
     '2025-12-01 10:30:00', '2026-01-08 16:00:00', '2025-12-01 11:00:00', 1, 1, '1,4'),

    (5, '使用 Docker Compose 编排 Spring Boot 微服务',
     '手把手教你用 Docker Compose 将 Spring Boot 应用、MySQL、Redis、Nginx 组合成完整的微服务部署方案。',
     '## Docker Compose 微服务部署\n\n### 项目结构\n\n```\nproject/\n├── docker-compose.yml\n├── app/\n│   └── Dockerfile\n├── nginx/\n│   └── nginx.conf\n└── mysql/\n    └── init.sql\n```\n\n### docker-compose.yml\n\n```yaml\nversion: \"3.8\"\nservices:\n  app:\n    build: ./app\n    ports:\n      - \"8080:8080\"\n    depends_on:\n      - mysql\n      - redis\n    environment:\n      - SPRING_PROFILES_ACTIVE=prod\n\n  mysql:\n    image: mysql:8.0\n    volumes:\n      - mysql_data:/var/lib/mysql\n    environment:\n      MYSQL_ROOT_PASSWORD: secret\n      MYSQL_DATABASE: blog\n\n  redis:\n    image: redis:7-alpine\n    ports:\n      - \"6379:6379\"\n\n  nginx:\n    image: nginx:alpine\n    ports:\n      - \"80:80\"\n    volumes:\n      - ./nginx/nginx.conf:/etc/nginx/nginx.conf\n```\n\n### 一键启动\n\n```bash\ndocker compose up -d\n```',
     'https://picsum.photos/seed/docker/800/450', 512, b'1', b'1', b'1', b'1', b'1', b'0', b'0', b'0',
     '2026-01-20 14:00:00', '2026-02-10 09:15:00', '2026-01-20 15:00:00', 6, 1, '6,2,11'),

    (6, 'Vue 3 组合式 API 完全指南',
     '深入讲解 Vue 3 Composition API 的核心概念,包括 ref、reactive、computed、watch 的使用场景和最佳实践。',
     '## Vue 3 Composition API\n\n### 为什么要用组合式 API?\n\nOptions API 在组件复杂后,相关逻辑分散在 data/methods/computed 中,难以维护。Composition API 按逻辑关注点组织代码。\n\n### ref vs reactive\n\n```javascript\nimport { ref, reactive } from \"vue\";\n\n// 基本类型用 ref\nconst count = ref(0);\n\n// 对象用 reactive\nconst user = reactive({\n  name: \"张三\",\n  age: 25\n});\n```\n\n### 自定义 Hook\n\n```javascript\nexport function useCounter(initial = 0) {\n  const count = ref(initial);\n  const increment = () => count.value++;\n  const decrement = () => count.value--;\n  return { count, increment, decrement };\n}\n```\n\n### 与 TypeScript 配合\n\nVue 3 对 TypeScript 支持更好,推荐使用 `<script setup lang=\"ts\">`。',
     'https://picsum.photos/seed/vue3/800/450', 487, b'1', b'1', b'1', b'1', b'1', b'0', b'0', b'0',
     '2025-08-22 09:00:00', '2025-10-15 11:30:00', '2025-08-22 10:00:00', 4, 1, '7'),

    (7, '设计模式在 Spring 框架中的应用',
     '结合 Spring 源码分析常用设计模式:工厂模式、代理模式、观察者模式、模板方法模式等在框架中的精妙运用。',
     '## Spring 中的设计模式\n\n### 1. 工厂模式 — BeanFactory\n\nSpring IoC 容器本身就是一个超级工厂,管理所有 Bean 的创建和生命周期。\n\n```java\nApplicationContext context = new ClassPathXmlApplicationContext(\"beans.xml\");\nUserService service = context.getBean(UserService.class);\n```\n\n### 2. 代理模式 — AOP\n\nSpring AOP 通过 JDK 动态代理或 CGLIB 实现切面编程。\n\n```java\n@Aspect\n@Component\npublic class LogAspect {\n    @Around(\"execution(* com.blog.service..*.*(..))\")\n    public Object log(ProceedingJoinPoint pjp) throws Throwable {\n        long start = System.currentTimeMillis();\n        Object result = pjp.proceed();\n        log.info(\"耗时: {}ms\", System.currentTimeMillis() - start);\n        return result;\n    }\n}\n```\n\n### 3. 观察者模式 — ApplicationEvent\n\nSpring 事件机制实现组件间松耦合通信。\n\n### 4. 模板方法 — JdbcTemplate\n\n封装了 JDBC 样板代码,用户只需关注 SQL 和结果映射。',
     'https://picsum.photos/seed/designpattern/800/450', 395, b'1', b'1', b'1', b'1', b'1', b'1', b'0', b'0',
     '2025-07-15 13:00:00', '2025-09-22 17:00:00', '2025-07-15 14:00:00', 2, 1, '2,8,1'),

    (8, 'Java 并发编程:线程池核心原理与调优',
     '从 ThreadPoolExecutor 源码出发,解析线程池的工作流程、拒绝策略、参数配置,以及生产环境常见问题排查。',
     '## Java 线程池\n\n### 为什么不用 Executors 创建线程池?\n\n`Executors.newFixedThreadPool()` 使用无界队列,可能导致 OOM。推荐手动创建 `ThreadPoolExecutor`。\n\n### 核心参数\n\n```java\nnew ThreadPoolExecutor(\n    4,                // corePoolSize\n    8,                // maximumPoolSize\n    60L,              // keepAliveTime\n    TimeUnit.SECONDS,\n    new LinkedBlockingQueue<>(1000),  // 有界队列\n    new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略\n);\n```\n\n### 工作流程\n\n1. 核心线程未满 → 创建新线程\n2. 核心线程满 → 入队列\n3. 队列满 → 创建非核心线程(不超过 max)\n4. 全满 → 执行拒绝策略\n\n### 调优建议\n\n- IO 密集型:coreSize = CPU * 2\n- CPU 密集型:coreSize = CPU + 1\n- 使用有界队列,设置合理容量',
     'https://picsum.photos/seed/threadpool/800/450', 341, b'1', b'1', b'1', b'1', b'1', b'0', b'0', b'0',
     '2025-06-28 16:00:00', '2025-08-10 14:20:00', '2025-06-28 17:00:00', 1, 1, '1,9'),

    (9, '从零搭建 CI/CD 流水线:GitHub Actions 实践',
     '介绍如何使用 GitHub Actions 为 Spring Boot 项目搭建自动化构建、测试、部署流水线,实现代码推送即部署。',
     '## GitHub Actions CI/CD\n\n### Workflow 文件\n\n```yaml\nname: CI/CD Pipeline\non:\n  push:\n    branches: [main]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-java@v4\n        with:\n          java-version: 17\n          distribution: temurin\n\n      - name: Build & Test\n        run: mvn clean verify\n\n      - name: Build Docker Image\n        run: docker build -t myapp:${{ github.sha }} .\n\n      - name: Deploy\n        run: |\n          ssh deploy@server \"docker pull myapp && docker compose up -d\"\n```\n\n### 最佳实践\n\n1. 分离 build 和 deploy job\n2. 使用 GitHub Secrets 管理密钥\n3. 添加缓存加速构建\n4. 设置通知(Slack/邮件)',
     'https://picsum.photos/seed/cicd/800/450', 278, b'1', b'1', b'1', b'1', b'1', b'0', b'0', b'0',
     '2026-02-05 10:00:00', '2026-02-20 15:30:00', '2026-02-05 11:00:00', 6, 1, '6,11'),

    (10, '《Effective Java》读书笔记:最值得记住的 10 条建议',
     '精选 Joshua Bloch 经典著作《Effective Java》中最实用的编程建议,结合现代 Java 特性给出具体代码示例。',
     '## Effective Java 精选\n\n### 1. 用静态工厂方法代替构造器\n\n```java\npublic static Boolean valueOf(boolean b) {\n    return b ? Boolean.TRUE : Boolean.FALSE;\n}\n```\n\n### 2. 遇到多个构造器参数时考虑 Builder 模式\n\n```java\nUser user = User.builder()\n    .name(\"张三\")\n    .age(25)\n    .email(\"zhangsan@example.com\")\n    .build();\n```\n\n### 3. 用枚举实现单例\n\n```java\npublic enum Singleton {\n    INSTANCE;\n    public void doSomething() { ... }\n}\n```\n\n### 4. 优先使用泛型\n\n### 5. 用 Optional 代替 null\n\n```java\nOptional<User> user = userService.findById(id);\nuser.ifPresent(u -> log.info(\"Found: {}\", u.getName()));\n```\n\n### 6. 谨慎使用 Stream\n\n### 7. 优先使用标准异常\n\n### 8. 使用 try-with-resources\n\n### 9. 用接口定义类型\n\n### 10. 使类和成员的可访问性最小化',
     'https://picsum.photos/seed/effectivejava/800/450', 189, b'1', b'0', b'1', b'1', b'1', b'0', b'0', b'0',
     '2026-02-18 20:00:00', '2026-02-25 10:00:00', '2026-02-18 21:00:00', 7, 1, '1,8');
/*!40000 ALTER TABLE `t_blog` ENABLE KEYS */;

-- ----------------------------
-- 博客-标签关联表
-- ----------------------------
CREATE TABLE IF NOT EXISTS `t_blog_tags` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '关联表主键',
  `tag_id` int(11) DEFAULT NULL COMMENT '标签id',
  `blog_id` bigint(20) DEFAULT NULL COMMENT '博文id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

/*!40000 ALTER TABLE `t_blog_tags` DISABLE KEYS */;
REPLACE INTO `t_blog_tags` (`id`, `tag_id`, `blog_id`) VALUES
    (1, 1, 1),   -- Spring Boot 3.x → Java
    (2, 2, 1),   -- Spring Boot 3.x → Spring Boot
    (3, 1, 2),   -- JVM GC → Java
    (4, 10, 2),  -- JVM GC → JVM
    (5, 3, 3),   -- MySQL 索引 → MySQL
    (6, 1, 3),   -- MySQL 索引 → Java
    (7, 1, 4),   -- Redis 分布式锁 → Java
    (8, 4, 4),   -- Redis 分布式锁 → Redis
    (9, 6, 5),   -- Docker Compose → Docker
    (10, 2, 5),  -- Docker Compose → Spring Boot
    (11, 11, 5), -- Docker Compose → Linux
    (12, 7, 6),  -- Vue 3 → Vue.js
    (13, 2, 7),  -- 设计模式 in Spring → Spring Boot
    (14, 8, 7),  -- 设计模式 in Spring → 设计模式
    (15, 1, 7),  -- 设计模式 in Spring → Java
    (16, 1, 8),  -- 线程池 → Java
    (17, 9, 8),  -- 线程池 → 并发编程
    (18, 6, 9),  -- CI/CD → Docker
    (19, 11, 9), -- CI/CD → Linux
    (20, 1, 10), -- Effective Java → Java
    (21, 8, 10); -- Effective Java → 设计模式
/*!40000 ALTER TABLE `t_blog_tags` ENABLE KEYS */;

-- ----------------------------
-- 留言表
-- ----------------------------
CREATE TABLE IF NOT EXISTS `t_message` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '留言表主键id',
  `nickname` varchar(255) DEFAULT NULL COMMENT '昵称',
  `email` varchar(255) DEFAULT NULL COMMENT '邮箱',
  `content` varchar(255) DEFAULT NULL COMMENT '内容',
  `avatar` varchar(255) DEFAULT NULL COMMENT '头像',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `parent_message_id` bigint(20) DEFAULT NULL COMMENT '父留言id',
  `admin_message` bit(1) NOT NULL COMMENT '是否为管理员评论',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

/*!40000 ALTER TABLE `t_message` DISABLE KEYS */;
REPLACE INTO `t_message` (`id`, `nickname`, `email`, `content`, `avatar`, `create_time`, `parent_message_id`, `admin_message`) VALUES
    -- 顶级留言
    (1, '陈同学', 'chenxiao@163.com', '博主写得太好了,Spring Boot 3 那篇帮我顺利完成了项目迁移,感谢分享!', 'https://picsum.photos/seed/user1/100/100', '2026-01-20 10:15:00', -1, b'0'),
    (2, '李开发', 'lidev@gmail.com', '请问博主,Redis 分布式锁在集群环境下 RedLock 真的可靠吗?Martin Kleppmann 的那篇质疑文章怎么看?', 'https://picsum.photos/seed/user2/100/100', '2026-01-25 14:30:00', -1, b'0'),
    (3, 'tangredtea', 'tangredtea@gmail.com', '好问题!RedLock 确实存在争议,对于强一致性场景建议用 ZooKeeper 或 etcd。一般业务场景 Redisson 单节点锁就够用了。', 'https://picsum.photos/seed/admin/200/200', '2026-01-25 16:45:00', 2, b'1'),
    (4, '王小明', 'wangxm@qq.com', '刚入门 Java 后端,请问学习路线怎么规划比较好?看了博主的文章感觉知识体系很清晰。', 'https://picsum.photos/seed/user3/100/100', '2026-02-01 09:20:00', -1, b'0'),
    (5, 'tangredtea', 'tangredtea@gmail.com', '建议路线:Java 基础 → MySQL → Spring Boot → Redis → 项目实战。不用贪多,每个阶段做一个小项目巩固。', 'https://picsum.photos/seed/admin/200/200', '2026-02-01 11:00:00', 4, b'1'),
    (6, '赵工程师', 'zhaodev@outlook.com', '线程池那篇写得很清楚!补充一点:Spring Boot 中可以用 @Async + 自定义 ThreadPoolTaskExecutor 更方便。', 'https://picsum.photos/seed/user4/100/100', '2026-02-05 17:30:00', -1, b'0'),
    (7, '刘学生', 'liustudent@edu.cn', '大四准备春招了,看了设计模式那篇文章很有启发。请问面试一般会考哪些设计模式?', 'https://picsum.photos/seed/user5/100/100', '2026-02-10 20:15:00', -1, b'0'),
    (8, 'tangredtea', 'tangredtea@gmail.com', '面试高频:单例、工厂、策略、观察者、代理。结合 Spring 源码聊会加分不少,加油!', 'https://picsum.photos/seed/admin/200/200', '2026-02-10 22:00:00', 7, b'1'),
    (9, '周运维', 'zhouops@company.com', 'Docker Compose 那篇正好是我需要的,之前一直手动部署太痛苦了。已经按照教程跑起来了,完美!', 'https://picsum.photos/seed/user6/100/100', '2026-02-15 08:45:00', -1, b'0'),
    (10, '孙前端', 'sunfe@gmail.com', 'Vue 3 那篇讲得很透彻,特别是 ref 和 reactive 的区别。希望博主可以出一篇 Pinia 状态管理的教程。', 'https://picsum.photos/seed/user7/100/100', '2026-02-18 15:20:00', -1, b'0'),
    (11, '李开发', 'lidev@gmail.com', '感谢博主的回复!确实 Redisson 对大部分场景够用了。又学到了。', 'https://picsum.photos/seed/user2/100/100', '2026-01-26 09:00:00', 2, b'0'),
    (12, '吴 DBA', 'wudba@company.com', 'MySQL 索引优化那篇非常实用,我们线上也遇到过类似的深分页问题,游标方案确实好用。', 'https://picsum.photos/seed/user8/100/100', '2026-02-22 11:30:00', -1, b'0');
/*!40000 ALTER TABLE `t_message` ENABLE KEYS */;

-- ----------------------------
-- 友链表
-- ----------------------------
CREATE TABLE IF NOT EXISTS `t_friend` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '友链表主键',
  `blog_address` varchar(255) NOT NULL COMMENT '友链地址',
  `blog_name` varchar(255) NOT NULL COMMENT '友链名字',
  `picture_address` varchar(255) NOT NULL COMMENT '友链图标',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

/*!40000 ALTER TABLE `t_friend` DISABLE KEYS */;
REPLACE INTO `t_friend` (`id`, `blog_address`, `blog_name`, `picture_address`, `create_time`) VALUES
    (1, 'https://www.ruanyifeng.com/blog/', '阮一峰的网络日志', 'https://picsum.photos/seed/ruanyf/200/200', '2025-06-15 10:00:00'),
    (2, 'https://tech.meituan.com/', '美团技术团队', 'https://picsum.photos/seed/meituan/200/200', '2025-07-20 14:30:00'),
    (3, 'https://coolshell.cn/', '酷壳 – CoolShell', 'https://picsum.photos/seed/coolshell/200/200', '2025-08-10 09:00:00'),
    (4, 'https://javaguide.cn/', 'JavaGuide', 'https://picsum.photos/seed/javaguide/200/200', '2025-09-05 16:00:00'),
    (5, 'https://www.hollischuang.com/', 'Hollis的博客', 'https://picsum.photos/seed/hollis/200/200', '2025-10-12 11:20:00'),
    (6, 'https://mp.weixin.qq.com/s/xxx', '沉默王二', 'https://picsum.photos/seed/wanger/200/200', '2025-11-28 13:45:00');
/*!40000 ALTER TABLE `t_friend` ENABLE KEYS */;

-- ----------------------------
-- 系统配置表
-- ----------------------------
CREATE TABLE IF NOT EXISTS `t_settings` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '配置ID',
  `config_key` varchar(100) NOT NULL COMMENT '配置键名',
  `config_value` text COMMENT '配置值',
  `description` varchar(255) DEFAULT NULL COMMENT '配置说明',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_config_key` (`config_key`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='系统配置表';

INSERT IGNORE INTO `t_settings` (`config_key`, `config_value`, `description`) VALUES
('site.name', 'SpringBoot AI Blog', '网站名称'),
('site.description', '基于 Spring Boot + AI 的智能博客系统', '网站描述'),
('site.keywords', 'Spring Boot,Blog,AI,Java', '网站关键词'),
('site.logo', '', '网站Logo URL'),
('site.favicon', '', '网站图标 URL'),
('site.beian', '', '备案号'),
('site.footer', '© 2024 SpringBoot AI Blog. All rights reserved.', '页脚信息'),
('comment.enabled', 'true', '是否开启评论'),
('comment.audit', 'false', '评论是否需要审核'),
('ai.enabled', 'false', '是否启用AI功能'),
('ai.auto_summary', 'false', '是否自动生成摘要'),
('ai.auto_tags', 'false', '是否自动推荐标签');

-- ----------------------------
-- 操作日志表
-- ----------------------------
CREATE TABLE IF NOT EXISTS `t_operation_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID',
  `user_id` int(11) DEFAULT NULL COMMENT '操作用户ID',
  `username` varchar(100) DEFAULT NULL COMMENT '操作用户名',
  `operation` varchar(255) DEFAULT NULL COMMENT '操作描述',
  `method` varchar(500) DEFAULT NULL COMMENT '请求方法',
  `params` text COMMENT '请求参数',
  `ip` varchar(50) DEFAULT NULL COMMENT 'IP地址',
  `duration` int(11) DEFAULT NULL COMMENT '执行时长(ms)',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0失败 1成功',
  `error_msg` text COMMENT '错误信息',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_user_id` (`user_id`) USING BTREE,
  KEY `idx_create_time` (`create_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='操作日志表';

/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;


================================================
FILE: docker-compose.yml
================================================
version: '3.8'

services:
  # MySQL 数据库
  mysql:
    image: mysql:8.0
    container_name: blog-mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-root}
      MYSQL_DATABASE: blog
      MYSQL_CHARSET: utf8mb4
      MYSQL_COLLATION: utf8mb4_unicode_ci
    volumes:
      - mysql_data:/var/lib/mysql
      - ./blog.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "3306:3306"
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 20s
      retries: 10

  # Redis 缓存
  redis:
    image: redis:7-alpine
    container_name: blog-redis
    restart: always
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5

  # SpringBoot AI Blog 应用
  app:
    build: .
    container_name: blog-app
    restart: always
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      # 数据库配置
      DB_HOST: mysql
      DB_PORT: 3306
      DB_NAME: blog
      DB_USERNAME: root
      DB_PASSWORD: ${DB_PASSWORD:-root}
      
      # Redis 配置
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_PASSWORD: ${REDIS_PASSWORD:-}
      
      # AI 配置(可选)
      AI_API_KEY: ${AI_API_KEY:-}
      AI_API_URL: ${AI_API_URL:-https://api.openai.com/v1/chat/completions}
      AI_MODEL: ${AI_MODEL:-gpt-3.5-turbo}
      
      # JVM 参数
      JVM_OPTS: "-Xmx512m -Xms512m"
    ports:
      - "8080:8080"
    volumes:
      - ./logs:/logs

volumes:
  mysql_data:
  redis_data:


================================================
FILE: nginx.conf
================================================
http {
 include  mime.types;
 default_type application/octet-stream;
 sendfile  on;
 keepalive_timeout 65;
 gzip on;

 server {
  #监听的端口号
  listen  80;
  #设置访问的二级域名
  server_name www.host.com; // 你的域名

  location /{
  #配置访问的项目路径(注:这里重点)
  proxy_pass http://127.0.0.1:8080/
  # root html;
  # index index.html index.htm;
  proxy_set_header   Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
  client_max_body_size 100m;
  root html;
  index index.html index.htm;
  }

 }

================================================
FILE: pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.18</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com</groupId>
	<artifactId>blog</artifactId>
	<version>1.0.0</version>
	<name>Spring-Blog-1.0.0</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>21</java.version>
		<lombok.version>1.18.38</lombok.version>
	</properties>

	<dependencies>
		<!--Redis依赖-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>


		<!--这三个jar包作用是将markdown格式转成html格式-->
		<dependency>
			<groupId>com.atlassian.commonmark</groupId>
			<artifactId>commonmark</artifactId>
			<version>0.17.0</version>
		</dependency>

		<dependency>
			<groupId>com.atlassian.commonmark</groupId>
			<artifactId>commonmark-ext-heading-anchor</artifactId>
			<version>0.17.0</version>
		</dependency>
		<dependency>
			<groupId>com.atlassian.commonmark</groupId>
			<artifactId>commonmark-ext-gfm-tables</artifactId>
			<version>0.17.0</version>
		</dependency>
<!--分页插件-->
		<dependency>
			<groupId>com.github.pagehelper</groupId>
			<artifactId>pagehelper-spring-boot-starter</artifactId>
			<version>1.4.6</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.3.1</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
			<version>2.5.9</version>
		</dependency>

		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
			<version>8.2.0</version>
		</dependency>
<!-- lombok类-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
<!--测试依赖-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<!--连接池-->


		<!-- 添加对 JDBC 的支持 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<scope>compile</scope>
		</dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.18.0</version>
        </dependency>
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>4.5.14</version>
		</dependency>
		<!-- Spring Security Crypto for BCrypt -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-crypto</artifactId>
		</dependency>
		<!-- Validation -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>


	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.13.0</version>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
					<annotationProcessorPaths>
						<path>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
							<version>${lombok.version}</version>
						</path>
					</annotationProcessorPaths>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>


================================================
FILE: src/main/java/com/blog/BlogApplication.java
================================================
package com.blog;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;

/**
 * @author tangredtea
 */
@SpringBootApplication
@EnableCaching
@EnableScheduling
public class BlogApplication {

	public static void main(String[] args) {
		SpringApplication.run(BlogApplication.class, args);
	}

}


================================================
FILE: src/main/java/com/blog/aspect/LogAspect.java
================================================
package com.blog.aspect;

import com.blog.pojo.RequestLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

/**
 * 定义切面
 * 方便日志输出,对相对应的访问方法织入
 * @author tangredtea
 */
@Aspect
@Component
public class LogAspect {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 定义切入点
     */
    @Pointcut("execution(* com.blog.controller..*.*(..))")
    public void log(){}

    /**
     * 前置增强
     */
    @Before("log()")
    public void doBefore(JoinPoint joinPoint){
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //断言,若请求为异常,则中止后续执行
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURL().toString();
        String ip = request.getRemoteAddr();
        //获得类名.方法名
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        //获得方法参数
        Object[] args = joinPoint.getArgs();
        //构建请求
        RequestLog requestLog = new RequestLog(url, ip, classMethod, args);
        //打印请求信息
        logger.info("Request: {}", requestLog);
    }

    /**
     * 后置增强
     */
    @After("log()")
    public void doAfter(){
        logger.info("------------doAfter------------");
    }

    /**
     *目标方法正常完成后被织入
     */
    @AfterReturning(returning = "result", pointcut = "log()")
    public void doAfterReturn(Object result){
        //打印返回值
        logger.info("Result: {}", result);
    }
}


================================================
FILE: src/main/java/com/blog/config/RedisConfig.java
================================================
package com.blog.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author tangredtea
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        //Use Jackson 2Json RedisSerializer to serialize and deserialize the value of redis (default JDK serialization)
        Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //将类名称序列化到json串中,去掉会导致得出来的的是LinkedHashMap对象,直接转换实体对象会失败
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //设置输入时忽略JSON字符串中存在而Java对象实际没有的属性
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        //Use String RedisSerializer to serialize and deserialize the key value of redis
        RedisSerializer<? extends String> redisSerializer = new StringRedisSerializer();
        //key
        redisTemplate.setKeySerializer(redisSerializer);
        redisTemplate.setHashKeySerializer(redisSerializer);
        //value
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;

    }

}

================================================
FILE: src/main/java/com/blog/config/RedisKey.java
================================================
package com.blog.config;

/**
 * @author tangredtea
 */
public final class RedisKey {
    public static final String ARTICLE_VIEWS = "article_views";
    public static final String ARTICLE = "article";
    public static final String INDEXBLOG = "all_blogs";
    public static final String RECOMMENDBLOG = "recommend_blogs";
    public static final String MESSAGES = "messages";
    public static final String HOTBLOGS = "hot_blogs";
}


================================================
FILE: src/main/java/com/blog/config/SettingsConfig.java
================================================
package com.blog.config;

import lombok.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * @author tangredtea
 */
@Component
@Data
@NoArgsConstructor
@PropertySource(value= "classpath:messages.properties",encoding = "UTF-8")
public class SettingsConfig {

    @Value("${web_Name}")
    private String web_Name;

    @Value("${web_IndexName}")
    private String web_IndexName;

    @Value("${web_Keywords}")
    private String web_Keywords;

    @Value("${web_Description}")
    private String web_Description;

    @Value("${web_Ico}")
    private String web_Ico;

    @Value("${web_Github}")
    private String web_Github;

    @Value("${web_Csdn}")
    private String web_Csdn;

    @Value("${web_Bilibili}")
    private String web_Bilibili;

    @Value("${web_Wechat}")
    private String web_Wechat;

    @Value("${web_QQ}")
    private String web_QQ;

    @Value("${web_Logo}")
    private String web_Logo;

    @Value("${web_Background}")
    private String web_Background;

    @Value("${web_Home}")
    private String web_Home;

    @Value("${web_GeYan}")
    private String web_GeYan;

    @Value("${default_avatar}")
    private String default_avatar;

    @Value("${message_Background}")
    private String message_Background;

    @Value("${about_Background}")
    private String about_Background;

    @Value("${friend_Background}")
    private String friend_Background;

    @Value("${search_Background}")
    private String search_Background;

    @Value("${tags_Background}")
    private String tags_Background;

    @Value("${time_Background}")
    private String time_Background;

    @Value("${types_Background}")
    private String types_Background;

    @Value("${valine_AppID}")
    private String valine_AppID;

    @Value("${valine_AppKey}")
    private String valine_AppKey;

    @Value("${wx_Webhook}")
    private String wx_Webhook;
}


================================================
FILE: src/main/java/com/blog/config/WebMvcConfig.java
================================================
package com.blog.config;

import com.blog.interceptor.LoginInterceptor;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author tangredtea
 */
@Configuration
@ComponentScan(basePackages = "com.blog")
public class WebMvcConfig implements WebMvcConfigurer {
    /**
     * 注册自定义拦截器
     *
     * @param registry 拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/admin")
                .excludePathPatterns("/admin/login");
    }


}



================================================
FILE: src/main/java/com/blog/controller/SitemapController.java
================================================
package com.blog.controller;

import com.blog.service.SitemapService;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;

/**
 * 站点地图控制器
 * 提供 sitemap.xml 用于搜索引擎收录
 * @author tangredtea
 */
@Controller
public class SitemapController {

    @Resource
    private SitemapService sitemapService;
    
    /**
     * 生成站点地图
     * 访问地址: /sitemap.xml
     */
    @GetMapping(value = "/sitemap.xml", produces = MediaType.APPLICATION_XML_VALUE)
    @ResponseBody
    public String sitemap() {
        return sitemapService.generateSitemap();
    }
}


================================================
FILE: src/main/java/com/blog/controller/admin/AIController.java
================================================
package com.blog.controller.admin;

import com.blog.service.AIService;
import com.blog.util.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * AI 功能控制器
 * @author tangredtea
 */
@Slf4j
@Controller
@RequestMapping("/admin/ai")
public class AIController {

    @Resource
    private AIService aiService;

    /**
     * AI 助手页面
     */
    @GetMapping("/assistant")
    public String assistant() {
        return "admin/ai-assistant";
    }

    /**
     * 生成文章摘要
     */
    @PostMapping("/summary")
    @ResponseBody
    public CommonResult generateSummary(@RequestParam String content) {
        try {
            String summary = aiService.generateSummary(content);
            return CommonResult.success(summary);
        } catch (Exception e) {
            log.error("Generate summary failed", e);
            return CommonResult.error("生成摘要失败:" + e.getMessage());
        }
    }
    
    /**
     * 推荐标签
     */
    @PostMapping("/suggest-tags")
    @ResponseBody
    public CommonResult suggestTags(@RequestParam String title, 
                                     @RequestParam String content) {
        try {
            String[] tags = aiService.suggestTags(title, content);
            return CommonResult.success(tags);
        } catch (Exception e) {
            log.error("Suggest tags failed", e);
            return CommonResult.error("推荐标签失败:" + e.getMessage());
        }
    }
    
    /**
     * 文章质量评分
     */
    @PostMapping("/score")
    @ResponseBody
    public CommonResult scoreArticle(@RequestParam String title,
                                      @RequestParam String content) {
        try {
            AIService.ArticleScore score = aiService.scoreArticle(title, content);
            Map<String, Object> result = new HashMap<>();
            result.put("score", score.getScore());
            result.put("suggestion", score.getSuggestion());
            result.put("level", getScoreLevel(score.getScore()));
            return CommonResult.success(result);
        } catch (Exception e) {
            log.error("Score article failed", e);
            return CommonResult.error("评分失败:" + e.getMessage());
        }
    }
    
    /**
     * 检查 AI 状态
     */
    @GetMapping("/status")
    @ResponseBody
    public CommonResult checkStatus() {
        Map<String, Object> status = new HashMap<>();
        status.put("enabled", aiService.isEnabled());
        status.put("message", aiService.isEnabled() ? "AI 服务正常运行" : "AI 服务未配置");
        return CommonResult.success(status);
    }
    
    private String getScoreLevel(int score) {
        if (score >= 90) return "优秀";
        if (score >= 80) return "良好";
        if (score >= 70) return "合格";
        if (score >= 60) return "待改进";
        return "需优化";
    }
}


================================================
FILE: src/main/java/com/blog/controller/admin/AdminController.java
================================================
package com.blog.controller.admin;

import com.blog.entity.User;
import com.blog.service.*;
import com.blog.util.PasswordUtils;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Objects;

/**
 * 后台登录处理
 * @author tangredtea
 */
@Controller
@RequestMapping("/admin")
public class AdminController {

    @Resource
    private BlogService blogService;

    @Resource
    private FriendLinkService friendLinkService;

    @Resource
    private MessageService messageService;

    @Resource
    private TagService tagService;

    @Resource
    private TypeService typeService;

    @Resource
    private UserService userService;

    @Resource
    private AIService aiService;

    @GetMapping({"","/","/index","/login"})
    public String loginPage(HttpSession session, Model model) {
        if (null != session && session.getAttribute("user") != null){
            model.addAttribute("article_nums", blogService.countBlog());
            model.addAttribute("article_views", blogService.getTotalViews());
            model.addAttribute("avg_views", blogService.getAvgViews());
            model.addAttribute("friendLink_nums", friendLinkService.countFriendLink());
            model.addAttribute("message_nums", messageService.countMessage());
            model.addAttribute("tag_nums", tagService.countTag());
            model.addAttribute("type_nums", typeService.countType());
            model.addAttribute("user_nums", userService.countUser());
            model.addAttribute("ai_enabled", aiService.isEnabled());
            // 最近发布的文章(取前5条)
            PageHelper.startPage(1, 5);
            model.addAttribute("recentBlogs", blogService.getAllBlog());
            return "admin/index";
        }
        return "admin/login";
    }

    @PostMapping("/login")
    public String login(@RequestParam String username,
                        @RequestParam String password,
                        HttpSession session,
                        RedirectAttributes attributes){
        User user = userService.checkUser(username, password);
        if(user != null){
            // 不在 session 中存储密码
            user.setPassword(null);
            session.setAttribute("user", user);
            return "redirect:/admin/index";
        }else {
            attributes.addFlashAttribute("msg", "用户名或密码错误");
            return "redirect:/admin";
        }
    }

    @GetMapping("/logout")
    public String logout(HttpSession session){
        session.removeAttribute("user");
        return "redirect:/admin";
    }

    @GetMapping("/users")
    public String users(@RequestParam(required = false,defaultValue = "1",value = "pageNum")int pageNum, Model model){
        PageHelper.startPage(pageNum, 5);
        List<User> allUser = userService.getUsers();
        PageInfo<User> pageInfo = new PageInfo<>(allUser);
        model.addAttribute("pageInfo", pageInfo);
        return "admin/users";
    }

    @GetMapping("/users/input")
    public String toAddUser(Model model){
        model.addAttribute("user", new User());
        return "admin/users-input";
    }

    @GetMapping("/users/{id}/input")
    public String toEditUser(@PathVariable Integer id, Model model){
        model.addAttribute("user", userService.getUserInfoById(id));
        return "admin/users-input";
    }

    @PostMapping("/users")
    public String addUser(User user, RedirectAttributes attributes){
        int nums = userService.getUserInfoByUsername(user.getUsername());
        if(nums != 0){
            attributes.addFlashAttribute("msg", "不能添加已存在的用户名");
            return "redirect:/admin/users/input";
        }else {
            attributes.addFlashAttribute("msg", "添加成功");
        }
        user.setPassword(PasswordUtils.encode(user.getPassword()));
        userService.saveUser(user);
        return "redirect:/admin/users";
    }

    @PostMapping("/users/{id}/delete")
    public String delete(@PathVariable Integer id, RedirectAttributes attributes){
        userService.deleteUser(id);
        attributes.addFlashAttribute("msg", "删除成功");
        return "redirect:/admin/users";
    }

    @PostMapping("/users/{id}")
    public String editUser(@PathVariable Integer id, User user, RedirectAttributes attributes){
        User beforeUser = userService.getUserInfoById(id);
        if (!Objects.equals(beforeUser.getUsername(), user.getUsername())){
            int nums = userService.getUserInfoByUsername(user.getUsername());
            if(nums != 0){
                attributes.addFlashAttribute("msg", "不能添加已存在的用户名");
                return "redirect:/admin/users/input";
            }else {
                attributes.addFlashAttribute("msg", "修改成功");
            }
        }
        // 如果密码为空或未修改,保留原密码;否则加密新密码
        if (user.getPassword() == null || user.getPassword().trim().isEmpty()){
            user.setPassword(beforeUser.getPassword());
        } else {
            user.setPassword(PasswordUtils.encode(user.getPassword()));
        }
        userService.updateUser(user);
        return "redirect:/admin/users";
    }
}


================================================
FILE: src/main/java/com/blog/controller/admin/BlogController.java
================================================
package com.blog.controller.admin;

import com.blog.config.RedisKey;
import com.blog.entity.Blog;
import com.blog.entity.User;
import com.blog.service.BlogService;
import com.blog.service.RedisService;
import com.blog.service.TagService;
import com.blog.service.TypeService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.util.List;

/**
 * @author tangredtea
 */
@Controller
@RequestMapping("/admin")
public class BlogController {

    @Resource
    private BlogService blogService;

    @Resource
    private TypeService typeService;

    @Resource
    private RedisService redisService;

    @Resource
    private TagService tagService;

    public void setTypeAndTag(Model model) {
        model.addAttribute("types", typeService.getAllType());
        model.addAttribute("tags", tagService.getAllTag());
    }

    /**
     * 后台显示博客列表
     * @param pageNum 页数
     * @param model 视图
     * @return 渲染视图
     */
    @GetMapping("/blogs")
    public String blogs(@RequestParam(required = false,defaultValue = "1",value = "pageNum")int pageNum, Model model){
        PageHelper.startPage(pageNum, 5);
        List<Blog> allBlog = blogService.getAllBlog();
        //得到分页结果对象
        PageInfo<? extends Blog> pageInfo = new PageInfo<>(allBlog);
        model.addAttribute("pageInfo", pageInfo);
        setTypeAndTag(model);
        return "admin/blogs";
    }

    /**
     * 按条件查询博客
     * @param blog 博文
     * @param pageNum 页数
     * @param model 视图
     * @return 渲染视图
     */
    @PostMapping("/blogs/search")
    public String searchBlogs(Blog blog, @RequestParam(required = false,defaultValue = "1",value = "pageNum")int pageNum, Model model){
        PageHelper.startPage(pageNum, 5);
        List<Blog> allBlog = blogService.searchAllBlog(blog);
        //得到分页结果对象
        PageInfo<? extends Blog> pageInfo = new PageInfo<>(allBlog);
        model.addAttribute("pageInfo", pageInfo);
        model.addAttribute("message", "查询成功");
        setTypeAndTag(model);
        return "admin/blogs";
    }

    /**
     * 新增博客页
     * @param model 视图
     * @return 渲染视图
     */
    @GetMapping("/blogs/input")
    public String toAddBlog(Model model){
        //返回一个blog对象给前端th:object
        model.addAttribute("blog", new Blog());
        setTypeAndTag(model);
        return "admin/blogs-input";
    }

    /**
     * 博文编辑页
     * @param id 博文id
     * @param model 视图
     * @return 渲染视图
     */
    @GetMapping("/blogs/{id}/input")
    public String toEditBlog(@PathVariable Long id, Model model){
        Blog blog = blogService.getBlog(id);
        //将tags集合转换为tagIds字符串
        blog.init();
        updateCache(blog);
        //返回一个blog对象给前端th:object
        model.addAttribute("blog", blog);
        setTypeAndTag(model);
        return "admin/blogs-input";
    }

    /**
     * 新增、编辑博客
     * @param blog 博文
     * @param session session
     * @param attributes 属性
     * @return 重定向到博文页
     */
    @PostMapping("/blogs")
    public String addBlog(Blog blog, HttpSession session, RedirectAttributes attributes){
        blog.setUser((User) session.getAttribute("user"));
        blog.setUserId(blog.getUser().getId());
        blog.setType(typeService.getType(blog.getType().getId()));
        blog.setTypeId(blog.getType().getId());
        blog.setTags(tagService.getTagByString(blog.getTagIds()));
        if (blog.getId() == null) {
            blogService.saveBlog(blog);
        } else {
            updateCache(blog);
            blogService.updateBlog(blog);
        }
        attributes.addFlashAttribute("msg", "新增成功");
        return "redirect:/admin/blogs";
    }

    @PostMapping("/blogs/{id}/delete")
    public String deleteBlogs(@PathVariable Long id, RedirectAttributes attributes){
        blogService.deleteBlog(id);
        deleteCache(id);
        attributes.addFlashAttribute("msg", "删除成功");
        return "redirect:/admin/blogs";
    }

    public void updateCache(Blog blog){
        if (redisService.hHasKey(RedisKey.ARTICLE, String.valueOf(blog.getId()))){
            redisService.hSet(RedisKey.ARTICLE, String.valueOf(blog.getId()), blog);
        }
    }

    public void deleteCache(Long id){
        if (redisService.hHasKey(RedisKey.ARTICLE, String.valueOf(id))){
            redisService.hDel(RedisKey.ARTICLE, String.valueOf(id));
        }
        if (redisService.hHasKey(RedisKey.ARTICLE_VIEWS, String.valueOf(id))){
            redisService.hDel(RedisKey.ARTICLE_VIEWS, String.valueOf(id));
        }
        redisService.set(RedisKey.INDEXBLOG, blogService.getIndexBlog());
        redisService.set(RedisKey.RECOMMENDBLOG, blogService.getAllRecommendBlog());
    }
}


================================================
FILE: src/main/java/com/blog/controller/admin/FriendLinkController.java
================================================
package com.blog.controller.admin;

import com.blog.entity.FriendLink;
import com.blog.service.FriendLinkService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;

/**
 * @author tangredtea
 */
@Controller
@RequestMapping("/admin")
public class FriendLinkController {

    @Resource
    private FriendLinkService friendLinkService;

    /**
     * 查询所有友链
     * @param model 视图
     * @param pageNum 页数
     * @return 渲染视图
     */
    @GetMapping({"/friendLinks","/friendlinks"})
    public String friend(Model model, @RequestParam(defaultValue = "1",value = "pageNum") Integer pageNum){
        PageHelper.startPage(pageNum,10);
        List<FriendLink> listFriendLink = friendLinkService.listFriendLink();
        PageInfo<FriendLink> pageInfo = new PageInfo<>(listFriendLink);
        model.addAttribute("pageInfo",pageInfo);
        return "admin/friendLinks";
    }

    /**
     * 跳转友链新增页面
     * @param model 视图
     * @return 友链新增页
     */
    @GetMapping("/friendLinks/input")
    public String input(Model model) {
        model.addAttribute("friendLink", new FriendLink());
        return "admin/friendLinks-input";
    }

    /**
     * 友链新增
     * @param friendLink 友链
     * @param result 结果
     * @param attributes 属性
     * @return 重定向到友链页
     */
    @PostMapping("/friendLinks")
    public String post(@Valid FriendLink friendLink, BindingResult result, RedirectAttributes attributes){
        FriendLink obj = friendLinkService.getFriendLinkByBlogAddress(friendLink.getBlogAddress());
        if (obj != null) {
            attributes.addFlashAttribute("msg", "不能添加相同的网址");
            return "redirect:/admin/friendLinks/input";
        }

        if(result.hasErrors()){
            return "admin/friendLinks-input";
        }
        friendLink.setCreateTime(new Date());
        int num = friendLinkService.saveFriendLink(friendLink);
        if (num == 0 ) {
            attributes.addFlashAttribute("msg", "新增失败");
        } else {
            attributes.addFlashAttribute("msg", "新增成功");
        }
        return "redirect:/admin/friendLinks";
    }

    /**
     * 跳转友链修改页面
     * @param id 友链id
     * @param model 视图
     * @return 友链修改页
     */
    @GetMapping("/friendLinks/{id}/input")
    public String editInput(@PathVariable Integer id, Model model) {
        model.addAttribute("friendLink", friendLinkService.getFriendLink(id));
        return "admin/friendLinks-input";
    }

    /**
     * 编辑修改友链
     * @param friendLink 友链
     * @param attributes 属性
     * @return 友链页
     */
    @PostMapping("/friendLinks/{id}")
    public String editPost(@Valid FriendLink friendLink, RedirectAttributes attributes) {
        int t = friendLinkService.updateFriendLink(friendLink);
        if (t == 0 ) {
            attributes.addFlashAttribute("msg", "编辑失败");
        } else {
            attributes.addFlashAttribute("msg", "编辑成功");
        }
        return "redirect:/admin/friendLinks";
    }

    /**
     * 删除友链
     * @param id 友链id
     * @param attributes 属性
     * @return 友链页
     */
    @PostMapping("/friendLinks/{id}/delete")
    public String delete(@PathVariable Integer id, RedirectAttributes attributes){
        friendLinkService.deleteFriendLink(id);
        attributes.addFlashAttribute("msg", "删除成功");
        return "redirect:/admin/friendLinks";
    }

}

================================================
FILE: src/main/java/com/blog/controller/admin/SettingsController.java
================================================
package com.blog.controller.admin;

import com.blog.config.SettingsConfig;
import com.blog.util.PropertiesUtil;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.IOException;


/**
 * @author tangredtea
 */
@Controller
@RequestMapping("/admin")
public class SettingsController {

    @Resource
    private SettingsConfig settings;

    @GetMapping("/settings")
    public String messages(Model model) throws IOException {
        model.addAttribute("settings",settings);
        return "admin/settings";
    }

    @PostMapping("/settings")
    public String message(@Valid SettingsConfig temp, RedirectAttributes attributes) throws IOException, IllegalAccessException {
        PropertiesUtil.write(settings.getClass(),temp);
        settings = temp;
        attributes.addFlashAttribute("msg", "编辑成功");
        return "redirect:/admin/settings";
    }


}


================================================
FILE: src/main/java/com/blog/controller/admin/TagController.java
================================================
package com.blog.controller.admin;

import com.blog.entity.Tag;
import com.blog.service.TagService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author tangredtea
 */
@Controller
@RequestMapping("/admin")
public class TagController {

    @Resource
    TagService tagService;

    @GetMapping("/tags")
    public String tags(@RequestParam(required = false,defaultValue = "1",value = "pageNum")int pageNum, Model model){
        PageHelper.startPage(pageNum, 5);
        List<Tag> allTag = tagService.getAllTag();
        PageInfo<Tag> pageInfo = new PageInfo<>(allTag);
        model.addAttribute("pageInfo", pageInfo);
        return "admin/tags";
    }

    @GetMapping("/tags/input")
    public String toAddTag(Model model){
        //返回一个tag对象给前端th:object
        model.addAttribute("tag", new Tag());
        return "admin/tags-input";
    }

    @GetMapping("/tags/{id}/input")
    public String toEditTag(@PathVariable Integer id, Model model){
        model.addAttribute("tag", tagService.getTag(id));
        return "admin/tags-input";
    }

    @PostMapping("/tags")
    public String addTag(Tag tag, RedirectAttributes attributes){
        Tag t = tagService.getTagByName(tag.getName());
        if(t != null){
            attributes.addFlashAttribute("msg", "不能添加重复的标签");
            return "redirect:/admin/tags/input";
        }else {
            attributes.addFlashAttribute("msg", "添加成功");
        }
        tagService.saveTag(tag);
        return "redirect:/admin/tags";
    }

    @PostMapping("/tags/{id}")
    public String editTag(@PathVariable Integer id, Tag tag, RedirectAttributes attributes){
        Tag t = tagService.getTagByName(tag.getName());
        if(t != null){
            attributes.addFlashAttribute("msg", "不能添加重复的标签");
            return "redirect:/admin/tags/input";
        }else {
            attributes.addFlashAttribute("msg", "修改成功");
        }
        tagService.updateTag(tag);
        return "redirect:/admin/tags";
    }

    @PostMapping("/tags/{id}/delete")
    public String delete(@PathVariable Integer id, RedirectAttributes attributes){
        tagService.deleteTag(id);
        attributes.addFlashAttribute("msg", "删除成功");
        return "redirect:/admin/tags";
    }
}


================================================
FILE: src/main/java/com/blog/controller/admin/TypeController.java
================================================
package com.blog.controller.admin;

import com.blog.entity.Type;
import com.blog.service.TypeService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author tangredtea
 */
@Controller
@RequestMapping("/admin")
public class TypeController {

    @Resource
    TypeService typeService;

    @GetMapping("/types")
    public String types(@RequestParam(required = false,defaultValue = "1",value = "pageNum")int pageNum, Model model){
        PageHelper.startPage(pageNum, 5);
        List<Type> allType = typeService.getAllType();
        PageInfo<Type> pageInfo = new PageInfo<>(allType);
        model.addAttribute("pageInfo", pageInfo);
        return "admin/types";
    }

    @GetMapping("/types/input")
    public String toAddType(Model model){
        //返回一个type对象给前端th:object
        model.addAttribute("type", new Type());
        return "admin/types-input";
    }

    @GetMapping("/types/{id}/input")
    public String toEditType(@PathVariable Integer id, Model model){
        model.addAttribute("type", typeService.getType(id));
        return "admin/types-input";
    }

    @PostMapping("/types")
    public String addType(Type type, RedirectAttributes attributes){
        Type t = typeService.getTypeByName(type.getName());
        if(t != null){
            attributes.addFlashAttribute("msg", "不能添加重复的分类");
            return "redirect:/admin/types/input";
        }else {
            attributes.addFlashAttribute("msg", "添加成功");
        }
        typeService.saveType(type);
        return "redirect:/admin/types";
    }

    @PostMapping("/types/{id}")
    public String editType(@PathVariable Integer id, Type type, RedirectAttributes attributes){
        Type t = typeService.getTypeByName(type.getName());
        if(t != null){
            attributes.addFlashAttribute("msg", "不能添加重复的分类");
            return "redirect:/admin/types/input";
        }else {
            attributes.addFlashAttribute("msg", "修改成功");
        }
        typeService.updateType(type);
        return "redirect:/admin/types";
    }

    @PostMapping("/types/{id}/delete")
    public String delete(@PathVariable Integer id, RedirectAttributes attributes){
        typeService.deleteType(id);
        attributes.addFlashAttribute("msg", "删除成功");
        return "redirect:/admin/types";
    }
}


================================================
FILE: src/main/java/com/blog/controller/blog/AboutShowController.java
================================================
package com.blog.controller.blog;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @author tangredtea
 */
@Controller
public class AboutShowController {

    @GetMapping("/about")
    public String about(){
        return "about";
    }
}


================================================
FILE: src/main/java/com/blog/controller/blog/ArchiveShowController.java
================================================
package com.blog.controller.blog;

import com.blog.service.BlogService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import javax.annotation.Resource;

/**
 * @author tangredtea
 */
@Controller
public class ArchiveShowController {

    @Resource
    private BlogService blogService;

    @GetMapping("/time")
    public String archives(Model model) {
        model.addAttribute("archiveMap", blogService.archiveBlog());
        model.addAttribute("blogCount", blogService.countBlog());
        return "time";
    }
}


================================================
FILE: src/main/java/com/blog/controller/blog/FriendLinkControllerShow.java
================================================
package com.blog.controller.blog;

import com.blog.service.FriendLinkService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import javax.annotation.Resource;

/**
 * @author tangredtea
 */
@Controller
public class FriendLinkControllerShow {

    @Resource
    private FriendLinkService friendLinkService;

    @GetMapping("/friends")
    public String friends(Model model) {
        model.addAttribute("friendLinks",friendLinkService.listFriendLink());
        return "friends";
    }
}


================================================
FILE: src/main/java/com/blog/controller/blog/IndexController.java
================================================
package com.blog.controller.blog;

import com.blog.config.RedisKey;
import com.blog.entity.Blog;
import com.blog.entity.Message;
import com.blog.service.AIService;
import com.blog.service.BlogService;
import com.blog.service.MessageService;
import com.blog.service.RedisService;
import com.blog.service.SmartSearchService;
import com.blog.util.CommonResult;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;

/**
 * @author tangredtea
 */
@Controller
public class IndexController {

    /** 缓存过期时间:5小时 */
    private static final long CACHE_TTL = 5 * 3600;

    @Resource
    private RedisService cache;

    @Resource
    private BlogService blogService;

    @Resource
    private MessageService messageService;

    @Resource
    private SmartSearchService smartSearchService;

    @Resource
    private AIService aiService;

    /**
     * 首页数据
     * @param model 视图
     * @param pageNum 分页
     * @return 渲染视图
     */
    @RequestMapping("/")
    public String toIndex(@RequestParam(required = false,defaultValue = "1") int pageNum, Model model){
        List<Blog> recommendBlog;
        List<Message> messages;
        List<Blog> hotBlogs;
        // 文章列表需要分页,不走缓存以保证 PageHelper 生效
        PageHelper.startPage(pageNum, 5);
        List<Blog> allBlog = blogService.getIndexBlog();
        if (cache.hasKey(RedisKey.RECOMMENDBLOG)){
            recommendBlog = (List<Blog>) cache.get(RedisKey.RECOMMENDBLOG);
        }else {
            recommendBlog = blogService.getAllRecommendBlog();
            cache.set(RedisKey.RECOMMENDBLOG, recommendBlog, CACHE_TTL);
        }
        if (cache.hasKey(RedisKey.MESSAGES)){
            messages = (List<Message>) cache.get(RedisKey.MESSAGES);
        }else {
            messages = messageService.findByIndexParentId();
            cache.set(RedisKey.MESSAGES, messages, CACHE_TTL);
        }
        if (cache.hasKey(RedisKey.HOTBLOGS)){
            hotBlogs = (List<Blog>) cache.get(RedisKey.HOTBLOGS);
        }else {
            hotBlogs = blogService.getHotBlog();
            cache.set(RedisKey.HOTBLOGS, hotBlogs, CACHE_TTL);
        }
        if (messages.size() >= 8){
            messages = messages.subList(0, 8);
        }
        PageInfo<? extends Blog> pageInfo = new PageInfo<>(allBlog);
        model.addAttribute("messages", messages);
        model.addAttribute("pageInfo", pageInfo);
        model.addAttribute("recommendBlogs", recommendBlog);
        model.addAttribute("hotBlogs", hotBlogs);
        return "index";
    }

    @GetMapping("/search")
    public String search(@RequestParam(required = false,defaultValue = "1",value = "pageNum")int pageNum,
                         @RequestParam String query, Model model){

        PageHelper.startPage(pageNum, 5);
        List<Blog> searchBlog = blogService.getSearchBlog(query);
        PageInfo<? extends Blog> pageInfo = new PageInfo<>(searchBlog);
        model.addAttribute("pageInfo", pageInfo);
        model.addAttribute("query", query);
        return "search";
    }

    @GetMapping("/blog/{id}")
    public String getBlog(@PathVariable Long id, Model model){
        Blog blog;
        if (cache.hHasKey(RedisKey.ARTICLE, String.valueOf(id))){
            blog = (Blog) cache.hGet(RedisKey.ARTICLE, String.valueOf(id));
        }else {
            blog = blogService.getDetailedBlog(id);
            cache.hSet(RedisKey.ARTICLE, String.valueOf(id), blog);
        }
        if (!cache.hHasKey(RedisKey.ARTICLE_VIEWS, String.valueOf(id))){
            cache.hSet(RedisKey.ARTICLE_VIEWS, String.valueOf(id), blog.getViews());
        }
        cache.hIncr(RedisKey.ARTICLE_VIEWS, String.valueOf(id), 1L);
        blog.setViews((Integer) cache.hGet(RedisKey.ARTICLE_VIEWS, String.valueOf(id)));
        model.addAttribute("blog", blog);
        try {
            List<Blog> relatedBlogs = smartSearchService.getRelatedBlogs(id, 4);
            model.addAttribute("relatedBlogs", relatedBlogs);
        } catch (Exception e) {
            model.addAttribute("relatedBlogs", Collections.emptyList());
        }
        return "blog";
    }

    @GetMapping("/api/search/suggestions")
    @ResponseBody
    public List<String> searchSuggestions(@RequestParam String query) {
        try {
            return smartSearchService.getSearchSuggestions(query);
        } catch (Exception e) {
            return Collections.emptyList();
        }
    }

    /**
     * AI 文章问答接口
     */
    @PostMapping("/api/ai/chat")
    @ResponseBody
    public CommonResult aiChat(@RequestParam Long blogId, @RequestParam String question) {
        try {
            if (question == null || question.trim().isEmpty()) {
                return CommonResult.error("请输入问题");
            }
            Blog blog;
            if (cache.hHasKey(RedisKey.ARTICLE, String.valueOf(blogId))) {
                blog = (Blog) cache.hGet(RedisKey.ARTICLE, String.valueOf(blogId));
            } else {
                blog = blogService.getDetailedBlog(blogId);
            }
            if (blog == null) {
                return CommonResult.error("文章不存在");
            }
            String answer = aiService.chatAboutArticle(blog.getTitle(), blog.getContent(), question.trim());
            return CommonResult.success(answer);
        } catch (Exception e) {
            return CommonResult.error("AI 暂时无法回答,请稍后再试");
        }
    }

    /**
     * 公开的 AI 状态接口
     */
    @GetMapping("/api/ai/status")
    @ResponseBody
    public CommonResult aiStatus() {
        java.util.Map<String, Object> status = new java.util.HashMap<>();
        status.put("enabled", aiService.isEnabled());
        return CommonResult.success(status);
    }
}


================================================
FILE: src/main/java/com/blog/controller/blog/MessageController.java
================================================
package com.blog.controller.blog;

import com.blog.config.RedisKey;
import com.blog.config.SettingsConfig;
import com.blog.entity.Message;
import com.blog.entity.User;
import com.blog.service.MessageService;
import com.blog.service.RedisService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.util.List;

/**
 * @author tangredtea
 */
@Controller
public class MessageController {

    @Resource
    private RedisService redisService;

    @Resource
    private SettingsConfig settingsConfig;

    @Resource
    private MessageService messageService;

    /**
     * 查询留言
     * @param model 视图
     * @return 渲染视图
     */
    @GetMapping({"/messageComment","/message"})
    public String messages(Model model) {
        List<Message> messages = messageService.listMessage();
        model.addAttribute("messages", messages);
        return "message";
    }

    /**
     * 新增留言
     * @param message 留言
     * @param session 用户
     * @param model 视图
     * @return 渲染视图
     */
    @PostMapping("/message")
    public String post(Message message, HttpSession session, Model model) {
        User user = (User) session.getAttribute("user");
        //设置头像
        if (user != null) {
            message.setAdminMessage(true);
            message.setAvatar(user.getAvatar());
        } else {
            message.setAvatar(settingsConfig.getDefault_avatar());
        }
        if (message.getParentMessage().getId() != null) {
            message.setParentMessageId(message.getParentMessage().getId());
        }
        messageService.saveMessage(message);
        List<Message> messages = messageService.listMessage();
        model.addAttribute("messages", messages);
        return "message";
    }

    /**
     * 删除留言(需要管理员权限)
     * @param id 留言id
     * @return 渲染视图
     */
    @PostMapping("/admin/messages/{id}/delete")
    public String delete(@PathVariable Long id){
        messageService.deleteMessage(id);
        deleteCache();
        return "redirect:/messageComment";
    }

    public void deleteCache(){
        redisService.set(RedisKey.MESSAGES, messageService.findByIndexParentId());
    }
}

================================================
FILE: src/main/java/com/blog/controller/blog/TagShowController.java
================================================
package com.blog.controller.blog;

import com.blog.entity.Blog;
import com.blog.entity.Tag;
import com.blog.service.BlogService;
import com.blog.service.TagService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author tangredtea
 */
@Controller
public class TagShowController {

    @Resource
    private TagService tagService;

    @Resource
    private BlogService blogService;

    @GetMapping(value = {"/tags/{id}","/tags"})
    public String types(@PathVariable(required = false) Integer id, @RequestParam(required = false,defaultValue = "1",value = "pageNum")int pageNum,
                        Model model){
        //开启分页
        PageHelper.startPage(pageNum, 100);
        List<Tag> tags = tagService.getBlogTag();
        //从导航点过来的
        if (id == null){
            id = tags.get(0).getId();
        }
        List<Blog> blogs = blogService.getByTagId(id);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
        PageInfo<Blog> pageInfo = new PageInfo<>(blogs);
        model.addAttribute("tags", tags);
        model.addAttribute("pageInfo", pageInfo);
        model.addAttribute("activeTagId", id);
        return "tags";
    }
}


================================================
FILE: src/main/java/com/blog/controller/blog/TypeShowController.java
================================================
package com.blog.controller.blog;

import com.blog.entity.Blog;
import com.blog.entity.Type;
import com.blog.service.BlogService;
import com.blog.service.TypeService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author tangredtea
 */
@Controller
public class TypeShowController {

    @Resource
    private TypeService typeService;

    @Resource
    private BlogService blogService;

    @GetMapping(value = {"/types/{id}","/types"})
    public String types(@PathVariable(required = false) Integer id, @RequestParam(required = false,defaultValue = "1",value = "pageNum")int pageNum,
                        Model model){
        //开启分页
        PageHelper.startPage(pageNum, 100);
        List<Type> types = typeService.getBlogType();
        //从导航点过来的
        if (id == null){
            id = types.get(0).getId();
            System.out.println(id);
        }
        List<Blog> blogs = blogService.getByTypeId(id);
        PageInfo<Blog> pageInfo = new PageInfo<>(blogs);
        model.addAttribute("types", types);
        model.addAttribute("pageInfo", pageInfo);
        model.addAttribute("activeTypeId", id);
        return "types";
    }
}


================================================
FILE: src/main/java/com/blog/controller/common/ControllerExceptionHandler.java
================================================
package com.blog.controller.common;

import com.blog.config.SettingsConfig;
import com.blog.pojo.WebhookMessage;
import com.blog.util.WxChatbotClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * 非状态码跳转到自定义的error页面
 * 状态码自动处理到相对应的状态码页面
 * 拦截所有controller抛出的异常,对异常进行统一的处理
 * @author tangredtea
 */
@ControllerAdvice
public class ControllerExceptionHandler {

    @Resource
    private SettingsConfig settings;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 表示该方法可以处理所有类型异常
     * @param request 请求
     * @param e 异常
     * @return 错误页面
     * @throws Exception 异常
     */
    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(HttpServletRequest request, Exception e) throws Exception {
        //日志打印异常信息
        logger.error("Request url: {}, Request ip: {}, Exception: {} ", request.getRequestURI(), request.getRemoteAddr(), e.getMessage());
        //不处理带有ResponseStatus注解的异常
        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
            throw e;
        }
        if (!"0".equals(settings.getWx_Webhook())){
            WebhookMessage message = new WebhookMessage();
            message.setText("博客异常请求通知\n请求url:" + request.getRequestURI() + "\n请求ip:" + request.getRemoteAddr() + "\n错误原因:" + e.getMessage());
            WxChatbotClient.send(settings.getWx_Webhook(), message);
        }
        //返回异常信息到自定义error页面
        ModelAndView mv = new ModelAndView();
        mv.addObject("url", request.getRequestURI());
        mv.addObject("exception",e);
        mv.setViewName("error/error");
        return mv;
    }

}


================================================
FILE: src/main/java/com/blog/dao/BlogDao.java
================================================
package com.blog.dao;

import com.blog.entity.Blog;
import com.blog.entity.BlogAndTag;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;

/**
 * @author tangredtea
 */
@Mapper
@Repository
public interface BlogDao {
    /**
     * 后台展示博客
     * @param id 博客id
     * @return 博文
     */
    Blog getBlog(Long id);

    /**
     *  博客详情
     * @param id 博客id
     * @return 博文
     */
    Blog getDetailedBlog(@Param("id") Long id);

    /**
     * 得到所有博客
     * @return 博客列表
     */
    List<Blog> getAllBlog();

    /**
     * 得到所有已发布博客
     * @return 博客列表
     */
    List<Blog> getAllPublishedBlogs();

    /**
     * 原子性增加浏览量(线程安全)
     * @param id 博客id
     * @return 修改状态
     */
    int incrementViews(@Param("id") Long id);

    /**
     * 根据类型id获取博客
     * @param typeId 类型id
     * @return 博客列表
     */
    List<Blog> getByTypeId(Integer typeId);

    /**
     * 根据标签id获取博客
     * @param tagId 标签id
     * @return 博客列表
     */
    List<Blog> getByTagId(Integer tagId);

    /**
     * 主页博客展示
     * @return 博文列表
     */
    List<Blog> getIndexBlog();

    /**
     * 推荐博客展示
     * @return 博文列表
     */
    List<Blog> getAllRecommendBlog();

    /**
     * 全局搜索博客
     * @param query 关键字
     * @return 博文列表
     */
    List<Blog> getSearchBlog(String query);

    /**
     * 后台根据标题、分类、推荐搜索博客
     * @param blog 博文
     * @return 博文列表
     */
    List<Blog> searchAllBlog(Blog blog);

    /**
     * 查询所有年份,返回一个集合
     * @return 日期值
     */
    List<String> findGroupYear();

    /**
     *  按年份查询博客
     * @param year 年份
     * @return 博文列表
     */
    List<Blog> findByYear(@Param("year") String year);

    /**
     * 保存博客
     * @param blog 博文
     * @return 状态值
     */
    int saveBlog(Blog blog);

    /**
     * 保存博客和标签
     * @param blogAndTag 博文和标签
     * @return 状态值
     */
    int saveBlogAndTag(BlogAndTag blogAndTag);

    /**
     * 删除博客标签关联
     * @param blogId 博文id
     * @return 状态值
     */
    int deleteBlogAndTagByBlogId(@Param("blogId") Long blogId);

    /**
     * 批量增加浏览量
     * @param id 博客id
     * @param increment 增量
     * @return 修改状态
     */
    int addViews(@Param("id") Long id, @Param("increment") int increment);

    /**
     * 更新博客
     * @param blog 博文
     * @return 状态值
     */
    int updateBlog(Blog blog);

    /**
     * 删除博客
     * @param id 博文id
     * @return 状态值
     */
    int deleteBlog(Long id);

    /**
     * 热门博客推荐
     * @return 博文列表
     */
    List<Blog> getHotBlog();

    /**
     * 得到博文数量
     * @return 数量
     */
    int getCount();

    /**
     * 得到阅读数
     * @return 阅读量
     */
    int getViews();

    /**
     * 得到平均阅读数
     * @return 阅读量
     */
    int getAvgViews();
}


================================================
FILE: src/main/java/com/blog/dao/FriendLinkDao.java
================================================
package com.blog.dao;

import com.blog.entity.FriendLink;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;

/**
 * @author tangredtea
 */
@Mapper
@Repository
public interface FriendLinkDao {
    /**
     * 列出友情链接
     * @return 友链列表
     */
    List<FriendLink> listFriendLink();

    /**
     * 保存友情链接
     * @param friendLink 友链
     * @return 状态值
     */
    int saveFriendLink(FriendLink friendLink);

    /**
     * 得到数量
     * @return 数量
     */
    int getCount();

    /**
     * 获取友情链接
     * @param id 友链id
     * @return 友链
     */
    FriendLink getFriendLink(Integer id);

    /**
     * 通过地址得到友情链接
     * @param blogAddress 友链地址
     * @return 友链
     */
    FriendLink getFriendLinkByBlogAddress(String blogAddress);

    /**
     * 更新友情链接
     * @param friendLink 友链
     * @return 状态值
     */
    int updateFriendLink(FriendLink friendLink);

    /**
     * 删除友情链接
     * @param id 友链id
     */
    void deleteFriendLink(Integer id);

}

================================================
FILE: src/main/java/com/blog/dao/MessageDao.java
================================================
package com.blog.dao;

import com.blog.entity.Message;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;

/**
 * @author tangredtea
 */
@Mapper
@Repository
public interface MessageDao {

    /**
     * 添加一个留言
     * @param message 留言
     * @return 状态值
     */
    int saveMessage(Message message);

    /**
     * 得到数量
     * @return 数量
     */
    int getCount();

    /**
     * 查询首页推荐留言
     * @param ParentId 留言父id
     * @return 留言列表
     */
    List<Message> findByIndexParentId(@Param("ParentId") Long ParentId);

    /**
     * 查询留言
     * @param ParentId 留言父id
     * @return 留言列表
     */
    List<Message> findByParentIdNull(@Param("ParentId") Long ParentId);

    /**
     * 查询一级回复
     * @param id 留言id
     * @return 留言列表
     */
    List<Message> findByParentIdNotNull(@Param("id") Long id);

    /**
     * 查询二级以及所有子集回复
     * @param childId 留言id
     * @return 留言列表
     */
    List<Message> findByReplayId(@Param("childId") Long childId);

    /**
     * 删除评论
     * @param id 留言id
     */
    void deleteMessage(Long id);

}


================================================
FILE: src/main/java/com/blog/dao/TagDao.java
================================================
package com.blog.dao;

import com.blog.entity.Tag;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;

/**
 * @author tangredtea
 */
@Mapper
@Repository
public interface TagDao {
    /**
     * 保存标签
     * @param tag 标签
     * @return 状态值
     */
    int saveTag(Tag tag);

    /**
     * 得到数量
     * @return 数量
     */
    int getCount();

    /**
     * 得到标签
     * @param id 标签id
     * @return 标签
     */
    Tag getTag(Integer id);

    /**
     * 通过名字获取标签
     * @param name 名字
     * @return 标签
     */
    Tag getTagByName(String name);

    /**
     * 得到所有标签
     * @return 标签列表
     */
    List<Tag> getAllTag();

    /**
     * 首页展示博客标签
     * @return 标签列表
     */
    List<Tag> getBlogTag();

    /**
     * 更新标签
     * @param tag 标签
     * @return 状态值
     */
    int updateTag(Tag tag);

    /**
     * 删除标签
     * @param id 标签id
     * @return 状态值
     */
    int deleteTag(Integer id);
}


================================================
FILE: src/main/java/com/blog/dao/TypeDao.java
================================================
package com.blog.dao;

import com.blog.entity.Type;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author tangredtea
 */
@Mapper
@Repository
public interface TypeDao {
    /**
     * 保存分类
     * @param type 分类
     * @return 状态值
     */
    int saveType(Type type);

    /**
     * 得到数量
     * @return 数量
     */
    int getCount();

    /**
     * 得到分类
     * @param id 分类id
     * @return 分类
     */
    Type getType(Integer id);

    /**
     * 通过分类名搜索
     * @param name 分类名
     * @return 分类
     */
    Type getTypeByName(String name);

    /**
     * 得到所有分类
     * @return 分类列表
     */
    List<Type> getAllType();

    /**
     * 首页右侧展示type对应的博客数量
     * @return 分类列表
     */
    List<Type> getBlogType();

    /**
     * 更新分类
     * @param type 分类
     * @return 状态值
     */
    int updateType(Type type);

    /**
     * 删除分类
     * @param id 分类id
     * @return 状态值
     */
    int deleteType(Integer id);
}


================================================
FILE: src/main/java/com/blog/dao/UserDao.java
================================================
package com.blog.dao;

import com.blog.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author tangredtea
 */
@Mapper
@Repository
public interface UserDao {
    /**
     * 查询用户登录
     * @param username 账号
     * @param password 密码
     * @return user
     */
    User queryByUsernameAndPassword(@Param("username") String username, @Param("password") String password);

    /**
     * 通过用户名查询用户
     * @param username 用户名
     * @return user
     */
    User queryByUsername(@Param("username") String username);

    /**
     * 通过id查询用户信息
     * @param id 用户id
     * @return user
     */
    User getUserInfoById(Integer id);

    /**
     * 得到所有用户
     * @return 标签列表
     */
    List<User> getAllUser();

    /**
     * 修改用户信息
     * @param user 用户
     * @return boolean
     */
    int updateUser(User user);

    /**
     * 删除用户
     * @param id 用户id
     * @return 状态值
     */
    int deleteUser(Integer id);

    /**
     * 保存用户
     * @param user 用户
     * @return 状态值
     */
    int saveUser(User user);

    /**
     * 得到数量
     * @return 数量
     */
    int getCount();

    /**
     * 查询用户名数量
     * @param name 用户名
     * @return 数量
     */
    int getUserInfoByUsername(String name);
}


================================================
FILE: src/main/java/com/blog/entity/Blog.java
================================================
package com.blog.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author tangredtea
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog implements Serializable {

    private Long id;
    private String title;
    private String content;
    private String firstPicture;
    private Integer views;
    private Boolean flag;
    private Boolean appreciation;
    private Boolean shareStatement;
    private Boolean commentable;
    private Boolean published;      // 是否发布(true=已发布,false=草稿)
    private Boolean recommend;      // 是否推荐
    private Boolean isDeleted;      // 是否删除(软删除)
    private Boolean isTop;          // 是否置顶
    private String password;        // 文章密码(为空表示公开)
    private Date publishTime;       // 发布时间
    private Date createTime;
    private Date updateTime;

    /**
     * 这个属性用来在mybatis中进行连接查询的
     */
    private Integer typeId;
    private Integer userId;

    /**
     * 获取多个标签的id
     */
    private String tagIds;

    private String description;

    private Type type;

    private User user;

    private List<Tag> tags = new ArrayList<>();

    public void init(){
        this.tagIds = tagsToIds(this.getTags());
    }

    /**
     * 将tags集合转换为tagIds字符串形式:“1,2,3”,用于编辑博客时显示博客的tag
     * @param tags 标签
     * @return 标签id
     */
    private String tagsToIds(List<Tag> tags){
        if(!tags.isEmpty()){
            StringBuilder ids = new StringBuilder();
            boolean flag = false;
            for(Tag tag: tags){
                if(flag){
                    ids.append(",");
                }else {
                    flag = true;
                }
                ids.append(tag.getId());
            }
            return ids.toString();
        }else {
            return tagIds;
        }
    }
    public String tagsToNames(){
        if(!tags.isEmpty()){
            StringBuilder ids = new StringBuilder();
            boolean flag = false;
            for(Tag tag: tags){
                if(flag){
                    ids.append(",");
                }else {
                    flag = true;
                }
                ids.append(tag.getName());
            }
            return ids.toString();
        }else {
            return "";
        }
    }
}


================================================
FILE: src/main/java/com/blog/entity/BlogAndTag.java
================================================
package com.blog.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * 把博客和标签关系存到数据库中使用的类
 * @author tangredtea
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BlogAndTag implements Serializable {

    private Integer tagId;

    private Long blogId;
}


================================================
FILE: src/main/java/com/blog/entity/FriendLink.java
================================================
package com.blog.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

/**
 * @author tangredtea
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FriendLink implements Serializable {

    private Integer id;
    private String blogName;
    private String blogAddress;
    private String pictureAddress;
    private Date createTime;

}


================================================
FILE: src/main/java/com/blog/entity/Message.java
================================================
package com.blog.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
留言类
 * @author tangredtea
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Message implements Serializable {

    private Long id;
    private String nickname;
    private String email;
    private String content;
    private String avatar;
    private Date createTime;
    private Long parentMessageId;
    private boolean adminMessage;

    /**
     * 回复评论
     */
    private List<Message> replyMessages = new ArrayList<>();
    private Message parentMessage;
    private String parentNickname;

}

================================================
FILE: src/main/java/com/blog/entity/Tag.java
================================================
package com.blog.entity;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * @author tangredtea
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Tag implements Serializable {
    private Integer id;
    private String name;
    private List<Blog> blogs = new ArrayList<>();
}


================================================
FILE: src/main/java/com/blog/entity/Type.java
================================================
package com.blog.entity;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * @author tangredtea
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Type implements Serializable {

    private Integer id;
    private String name;
    private List<Blog> blogs = new ArrayList<>();
}


================================================
FILE: src/main/java/com/blog/entity/User.java
================================================
package com.blog.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * @author tangredtea
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private Integer id;
    private String nickname;
    private String username;
    private String password;
    private String email;
    private String avatar;
    private List<Blog> blogs = new ArrayList<>();
}


================================================
FILE: src/main/java/com/blog/enums/BlogStatus.java
================================================
package com.blog.enums;

import lombok.Getter;

/**
 * 博客文章状态枚举
 * @author tangredtea
 */
@Getter
public enum BlogStatus {
    
    DRAFT(0, "草稿"),
    PUBLISHED(1, "已发布"),
    DELETED(2, "已删除(回收站)");
    
    private final int code;
    private final String desc;
    
    BlogStatus(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}


================================================
FILE: src/main/java/com/blog/exception/BusinessException.java
================================================
package com.blog.exception;

import lombok.Getter;

/**
 * 业务异常
 * @author tangredtea
 */
@Getter
public class BusinessException extends RuntimeException {

    private final String redirectUrl;

    public BusinessException(String message) {
        super(message);
        this.redirectUrl = "/admin";
    }

    public BusinessException(String message, String redirectUrl) {
        super(message);
        this.redirectUrl = redirectUrl;
    }
}


================================================
FILE: src/main/java/com/blog/exception/GlobalExceptionHandler.java
================================================
package com.blog.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

/**
 * 全局异常处理器
 * @author tangredtea
 */
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理所有异常
     */
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e, Model model) {
        log.error("系统异常", e);
        model.addAttribute("errorMsg", "系统繁忙,请稍后重试");
        return "error/error";
    }

    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public String handleBusinessException(BusinessException e, RedirectAttributes attributes) {
        log.warn("业务异常: {}", e.getMessage());
        attributes.addFlashAttribute("msg", e.getMessage());
        return "redirect:" + e.getRedirectUrl();
    }
}


================================================
FILE: src/main/java/com/blog/exception/NotFoundException.java
================================================
package com.blog.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

/**
 * 自定义NotFoundException异常,会跳转到404页面
 * @author tangredtea
 */
@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {

    public NotFoundException() {
    }

    public NotFoundException(String message) {
        super(message);
    }

    public NotFoundException(String message, Throwable cause) {
        super(message, cause);
    }

}


================================================
FILE: src/main/java/com/blog/interceptor/LoginInterceptor.java
================================================
package com.blog.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 登录拦截器
 * @author tangredtea
 */
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (request.getSession().getAttribute("user") == null){
            response.sendRedirect("/admin");
            return false;
        }
        return true;
    }

}

================================================
FILE: src/main/java/com/blog/pojo/RequestLog.java
================================================
package com.blog.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * 日志类,用于封装请求信息
 * @author tangredtea
 */
@Data
@AllArgsConstructor
public class RequestLog{
    private String url;
    private String ip;
    private String classMethod;
    private Object[] args;
}

================================================
FILE: src/main/java/com/blog/pojo/WebhookMessage.java
================================================
package com.blog.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;

/**
 * @author tangredtea
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class WebhookMessage {
    /**
     * the text to send
     */
    private String text;

    public Map<String, Object> toJsonString() {
        Map<String, Object> items = new HashMap<>();
        items.put("msgtype", "text");
        Map<String, Object> textContent = new HashMap<>();
        textContent.put("content", text);
        items.put("text", textContent);
        return items;
    }
}


================================================
FILE: src/main/java/com/blog/scheduled/Refresh.java
================================================
package com.blog.scheduled;

import com.blog.dao.BlogDao;
import com.blog.config.RedisKey;
import com.blog.entity.Blog;
import com.blog.service.BlogService;
import com.blog.service.MessageService;
import com.blog.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Map;

/**
 * @author tangredtea
 */
@Slf4j
@Component
public class Refresh {

    @Resource
    BlogDao blogDao;

    @Resource
    BlogService blogService;

    @Resource
    MessageService messageService;

    @Resource
    RedisService cache;

    /** 缓存过期时间:5小时(大于定时任务4小时间隔,保证定时任务异常时缓存也会过期) */
    private static final long CACHE_TTL = 5 * 3600;

    @PostConstruct
    public void init(){
        refreshCaches();
    }

    @Scheduled(cron = "0 0 0/4 * * ? ")
    public void execute() {
        // 博客刷新阅读量到数据库
        Map<String, Object> blogMap = cache.hGetAll(RedisKey.ARTICLE);
        Map<String, Object> viewMap = cache.hGetAll(RedisKey.ARTICLE_VIEWS);

        for (Map.Entry<String, Object> entry : blogMap.entrySet()) {
            String key = entry.getKey();
            Blog blog = (Blog) entry.getValue();
            Integer redisViews = (Integer) viewMap.get(key);

            if (redisViews != null && !redisViews.equals(blog.getViews())) {
                int increment = redisViews - blog.getViews();
                if (increment > 0) {
                    blogDao.addViews(blog.getId(), increment);
                    blog.setViews(redisViews);
                    cache.hSet(RedisKey.ARTICLE, key, blog);
                }
            }
        }
        refreshCaches();
    }

    private void refreshCaches() {
        cache.set(RedisKey.INDEXBLOG, blogService.getIndexBlog(), CACHE_TTL);
        cache.set(RedisKey.RECOMMENDBLOG, blogService.getAllRecommendBlog(), CACHE_TTL);
        cache.set(RedisKey.HOTBLOGS, blogService.getHotBlog(), CACHE_TTL);
        cache.set(RedisKey.MESSAGES, messageService.findByIndexParentId(), CACHE_TTL);
    }
}


================================================
FILE: src/main/java/com/blog/service/AIService.java
================================================
package com.blog.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

/**
 * AI 服务
 * 集成大模型 API 提供智能功能
 * @author tangredtea
 */
@Slf4j
@Service
public class AIService {

    @Value("${ai.api.key:}")
    private String apiKey;
    
    @Value("${ai.api.url:https://api.openai.com/v1/chat/completions}")
    private String apiUrl;
    
    @Value("${ai.model:gpt-3.5-turbo}")
    private String model;
    
    private RestTemplate restTemplate;
    private boolean aiEnabled = false;
    
    @PostConstruct
    public void init() {
        restTemplate = new RestTemplate();
        aiEnabled = !apiKey.isEmpty();
        if (aiEnabled) {
            log.info("AI service initialized with model: {}", model);
        } else {
            log.warn("AI service disabled: API key not configured");
        }
    }
    
    /**
     * 生成文章摘要
     * @param content 文章内容
     * @return 生成的摘要
     */
    public String generateSummary(String content) {
        if (!aiEnabled) {
            return generateLocalSummary(content);
        }
        
        try {
            String prompt = "请为以下文章生成一段简洁的摘要(100字以内):\n\n" + 
                           content.substring(0, Math.min(content.length(), 2000));
            
            return callAI(prompt);
        } catch (Exception e) {
            log.error("AI summary generation failed", e);
            return generateLocalSummary(content);
        }
    }
    
    /**
     * 生成文章标签建议
     * @param title 文章标题
     * @param content 文章内容
     * @return 标签建议数组
     */
    public String[] suggestTags(String title, String content) {
        if (!aiEnabled) {
            return new String[0];
        }
        
        try {
            String prompt = String.format(
                "请为以下文章推荐3-5个合适的标签,用逗号分隔:\n标题:%s\n内容:%s",
                title,
                content.substring(0, Math.min(content.length(), 1000))
            );
            
            String response = callAI(prompt);
            // Clean up: remove markdown formatting, headers, etc.
            response = response.replaceAll("\\*\\*[^*]*\\*\\*", "").trim();
            // Remove leading/trailing newlines and pick the last non-empty line (tags line)
            String[] lines = response.split("\\n");
            String tagLine = response;
            for (int i = lines.length - 1; i >= 0; i--) {
                if (!lines[i].trim().isEmpty()) {
                    tagLine = lines[i].trim();
                    break;
                }
            }
            String[] tags = tagLine.split("[,,]\\s*");
            // Filter out empty tags
            java.util.List<String> result = new java.util.ArrayList<>();
            for (String tag : tags) {
                String t = tag.trim();
                if (!t.isEmpty()) {
                    result.add(t);
                }
            }
            return result.toArray(new String[0]);
        } catch (Exception e) {
            log.error("AI tag suggestion failed", e);
            return new String[0];
        }
    }
    
    /**
     * 智能回复评论
     * @param comment 评论内容
     * @param articleTitle 文章标题
     * @return 回复内容
     */
    public String generateReply(String comment, String articleTitle) {
        if (!aiEnabled) {
            return "感谢您的评论!";
        }
        
        try {
            String prompt = String.format(
                "作为博主,请礼貌地回复以下评论(50字以内):\n文章:《%s》\n评论:%s",
                articleTitle,
                comment
            );
            
            return callAI(prompt);
        } catch (Exception e) {
            log.error("AI reply generation failed", e);
            return "感谢您的评论!";
        }
    }
    
    /**
     * 文章质量评分
     * @param title 标题
     * @param content 内容
     * @return 评分和建议
     */
    public ArticleScore scoreArticle(String title, String content) {
        if (!aiEnabled) {
            return new ArticleScore(70, "AI 服务未启用,使用默认评分");
        }
        
        try {
            String prompt = String.format(
                "请对以下博客文章进行评分(0-100分),并给出简要建议:\n标题:%s\n内容:%s\n\n格式:分数|建议",
                title,
                content.substring(0, Math.min(content.length(), 1500))
            );
            
            String response = callAI(prompt);
            // Try to extract score from response - look for "分数|建议" format first
            String[] parts = response.split("\\|", 2);

            int score = 70;
            String suggestion = "继续加油!";

            // Try to extract a number from the first part
            try {
                String numStr = parts[0].replaceAll("[^0-9]", "");
                if (!numStr.isEmpty()) {
                    int parsed = Integer.parseInt(numStr.substring(0, Math.min(numStr.length(), 3)));
                    if (parsed >= 0 && parsed <= 100) {
                        score = parsed;
                    }
                }
            } catch (NumberFormatException ignored) {}

            if (parts.length > 1) {
                suggestion = parts[1].replaceAll("\\*\\*[^*]*\\*\\*", "").trim();
            } else {
                // No pipe found - try to use the whole response as suggestion
                suggestion = response.replaceAll("\\*\\*[^*]*\\*\\*", "").replaceAll("\\d+\\s*分?", "").trim();
                if (suggestion.isEmpty()) suggestion = "继续加油!";
            }
            
            return new ArticleScore(score, suggestion);
        } catch (Exception e) {
            log.error("AI article scoring failed", e);
            return new ArticleScore(70, "评分服务暂时不可用");
        }
    }
    
    /**
     * 调用 AI API
     */
    private String callAI(String prompt) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setBearerAuth(apiKey);
        
        Map<String, Object> message = new HashMap<>();
        message.put("role", "user");
        message.put("content", prompt);
        
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("model", model);
        requestBody.put("messages", new Object[]{message});
        requestBody.put("temperature", 0.7);
        requestBody.put("max_tokens", 500);
        
        HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
        
        @SuppressWarnings("unchecked")
        Map<String, Object> response = restTemplate.postForObject(apiUrl, request, Map.class);
        
        if (response != null && response.containsKey("choices")) {
            @SuppressWarnings("unchecked")
            java.util.List<Map<String, Object>> choices = (java.util.List<Map<String, Object>>) response.get("choices");
            if (!choices.isEmpty()) {
                @SuppressWarnings("unchecked")
                Map<String, Object> choice = choices.get(0);
                @SuppressWarnings("unchecked")
                Map<String, String> message_ = (Map<String, String>) choice.get("message");
                String content = message_.get("content").trim();
                // Strip <think>...</think> blocks from reasoning models
                content = content.replaceAll("(?s)<think>.*?</think>", "").trim();
                return content;
            }
        }
        
        throw new RuntimeException("Invalid AI response");
    }
    
    /**
     * 本地摘要生成(备用方案)
     */
    private String generateLocalSummary(String content) {
        // 去除 HTML 标签
        String text = content.replaceAll("<[^>]+>", "");
        // 取前 150 字符
        if (text.length() > 150) {
            return text.substring(0, 150) + "...";
        }
        return text;
    }
    
    /**
     * 文章问答
     * @param articleTitle 文章标题
     * @param articleContent 文章内容
     * @param question 用户问题
     * @return AI 回答
     */
    public String chatAboutArticle(String articleTitle, String articleContent, String question) {
        if (!aiEnabled) {
            return "AI 服务未启用,暂时无法回答问题。请联系博主配置 AI 服务。";
        }

        try {
            // Truncate article content to fit in context
            String truncatedContent = articleContent.replaceAll("<[^>]+>", "");
            if (truncatedContent.length() > 2000) {
                truncatedContent = truncatedContent.substring(0, 2000) + "...";
            }

            String prompt = String.format(
                "你是一个博客文章的 AI 助手。根据以下文章内容回答用户的问题。\n" +
                "回答要求:简洁准确,控制在 200 字以内,使用中文。如果问题与文章无关,礼貌地引导用户提出与文章相关的问题。\n\n" +
                "文章标题:%s\n" +
                "文章内容:%s\n\n" +
                "用户问题:%s",
                articleTitle, truncatedContent, question
            );

            return callAI(prompt);
        } catch (Exception e) {
            log.error("AI chat failed", e);
            return "抱歉,AI 暂时无法回答您的问题,请稍后再试。";
        }
    }

    /**
     * 检查 AI 是否可用
     */
    public boolean isEnabled() {
        return aiEnabled;
    }
    
    /**
     * 文章评分结果
     */
    public static class ArticleScore {
        private final int score;
        private final String suggestion;
        
        public ArticleScore(int score, String suggestion) {
            this.score = score;
            this.suggestion = suggestion;
        }
        
        public int getScore() { return score; }
        public String getSuggestion() { return suggestion; }
    }
}


================================================
FILE: src/main/java/com/blog/service/BlogService.java
================================================
package com.blog.service;

import com.blog.entity.Blog;

import java.util.List;
import java.util.Map;

/**
 * @author tangredtea
 */
public interface BlogService {

    /**
     * 根据id查博文
     * @param id 博文id
     * @return 博文
     */
    Blog getBlog(Long id);

    /**
     * 前端展示博客
     * @param id 博文id
     * @return 博文
     */
    Blog getDetailedBlog(Long id);

    /**
     * 得到所有博文
     * @return 博文列表
     */
    List<Blog> getAllBlog();

    /**
     * 根据类型id获取博客
     * @param typeId 类型id
     * @return 博文列表
     */
    List<Blog> getByTypeId(Integer typeId);

    /**
     * 根据标签id获取博客
     * @param tagId 标签id
     * @return 博文列表
     */
    List<Blog> getByTagId(Integer tagId);

    /**
     * 主页博客展示
     * @return 博文列表
     */
    List<Blog> getIndexBlog();

    /**
     * 推荐博客展示
     * @return 博文列表
     */
    List<Blog> getAllRecommendBlog();

    /**
     * 全局搜索博客
     * @param query 关键字
     * @return 博文列表
     */
    List<Blog> getSearchBlog(String query);

    /**
     * 归档博客
     * @return 博文列表
     */
    Map<String,List<Blog>> archiveBlog();

    /**
     * 查询博客条数
     * @return 博文数目
     */
    int countBlog();

    /**
     * 保存博客
     * @param blog 博文
     * @return 状态值
     */
    int saveBlog(Blog blog);

    /**
     * 更新博客
     * @param blog 博文
     * @return 状态值
     */
    int updateBlog(Blog blog);

    /**
     * 删除博客
     * @param id 博文id
     * @return 状态值
     */
    int deleteBlog(Long id);

    /**
     * 后台根据标题、分类、推荐搜索博客
     * @param blog 博文
     * @return 博文列表
     */
    List<Blog> searchAllBlog(Blog blog);

    /**
     * 得到热门博客
     * @return 博文列表
     */
    List<Blog> getHotBlog();

    /**
     * 得到总浏览量
     * @return 总浏览量
     */
    int getTotalViews();

    /**
     * 得到平均浏览量
     * @return 平均浏览量
     */
    int getAvgViews();
}


================================================
FILE: src/main/java/com/blog/service/FriendLinkService.java
================================================
package com.blog.service;

import com.blog.entity.FriendLink;

import java.util.List;

/**
 * @author tangredtea
 */
public interface FriendLinkService {

    /**
     * 查询所有友链
     * @return 友链列表
     */
    List<FriendLink> listFriendLink();

    /**
     * 友链新增
     * @param friendLink 友链
     * @return 状态值
     */
    int saveFriendLink(FriendLink friendLink);

    /**
     * 根据id查询友链
     * @param id 友链id
     * @return 友链
     */
    FriendLink getFriendLink(Integer id);

    /**
     * 根据网址查询友链
     * @param blogAddress 友链网址
     * @return 友链
     */
    FriendLink getFriendLinkByBlogAddress(String blogAddress);

    /**
     * 编辑修改友链
     * @param friendLink 友链
     * @return 状态值
     */
    int updateFriendLink(FriendLink friendLink);

    /**
     * 删除友链
     * @param id 友链id
     */
    void deleteFriendLink(Integer id);

    /**
     * 得到友链数量
     * @return 数量
     */
    int countFriendLink();

}

================================================
FILE: src/main/java/com/blog/service/MessageService.java
================================================
package com.blog.service;

import com.blog.entity.Message;

import java.util.List;

/**
 * @author tangredtea
 */
public interface MessageService {

    /**
     * 查询留言列表
     * @return 留言列表
     */
    List<Message> listMessage();

    /**
     * 列出首页推荐留言
     * @return 留言列表
     */
    List<Message> findByIndexParentId();

    /**
     * 保存留言
     * @param message 留言
     * @return 状态
     */
    int saveMessage(Message message);

    /**
     * 删除留言
     * @param id 留言id
     */
    void deleteMessage(Long id);

    /**
     * 得到留言数量
     * @return 数量
     */
    int countMessage();

}


================================================
FILE: src/main/java/com/blog/service/RedisService.java
================================================
package com.blog.service;

import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Redis 操作服务接口
 * 提供常用的 Redis 数据操作功能
 * 
 * @author tangredtea
 * @since 2022-02-22
 */
@SuppressWarnings("all")
public interface RedisService {
    /**
     * 保存属性
     *
     * @param key   key值
     * @param value value值
     * @param time  时间戳
     */
    void set(String key, Object value, long time);

    /**
     * 保存属性
     *
     * @param key   key值
     * @param value value值
     */
    void set(String key, Object value);

    /**
     * 获取属性
     *
     * @param key key值
     * @return 返回对象
     */
    Object get(String key);

    /**
     * 删除属性
     *
     * @param key key值
     * @return 返回成功
     */
    Boolean del(String key);

    /**
     * 批量删除属性
     *
     * @param keys key值集合
     * @return 返回删除数量
     */
    Long del(List<String> keys);

    /**
     * 设置过期时间
     *
     * @param key  key值
     * @param time 时间戳
     * @return 返回成功
     */
    Boolean expire(String key, long time);

    /**
     * 获取过期时间
     *
     * @param key key值
     * @return 返回时间戳
     */
    Long getExpire(String key);

    /**
     * 判断key是否存在
     *
     * @param key key值
     * @return 返回
     */
    Boolean hasKey(String key);

    /**
     * 按delta递增
     *
     * @param key   key值
     * @param delta delta值
     * @return 返回递增后结果
     */
    Long incr(String key, long delta);

    /**
     * 按delta递减
     *
     * @param key   key值
     * @param delta delta值
     * @return 返回递减后结果
     */
    Long decr(String key, long delta);

    /**
     * 获取Hash结构中的属性
     *
     * @param key     外部key值
     * @param hashKey 内部key值
     * @return 返回内部key的value
     */
    Object hGet(String key, String hashKey);

    /**
     * 向Hash结构中放入一个属性
     *
     * @param key     外部key
     * @param hashKey 内部key
     * @param value   内部key的value
     * @param time    过期时间
     * @return 返回是否成功
     */
    Boolean hSet(String key, String hashKey, Object value, long time);

    /**
     * 向Hash结构中放入一个属性
     *
     * @param key     外部key
     * @param hashKey 内部key
     * @param value   内部key的value
     */
    void hSet(String key, String hashKey, Object value);

    /**
     * 直接获取整个Hash结构
     *
     * @param key 外部key值
     * @return 返回hashMap
     */
    Map hGetAll(String key);

    /**
     * 直接设置整个Hash结构
     *
     * @param key  外部key
     * @param map  hashMap值
     * @param time 过期时间
     * @return 返回是否成功
     */
    Boolean hSetAll(String key, Map<String, Object> map, long time);

    /**
     * 直接设置整个Hash结构
     *
     * @param key 外部key
     * @param map hashMap值
     */
    void hSetAll(String key, Map<String, ?> map);

    /**
     * 删除Hash结构中的属性
     *
     * @param key     外部key值
     * @param hashKey 内部key值
     */
    void hDel(String key, Object... hashKey);

    /**
     * 判断Hash结构中是否有该属性
     *
     * @param key     外部key
     * @param hashKey 内部key
     * @return 返回是否存在
     */
    Boolean hHasKey(String key, String hashKey);

    /**
     * Hash结构中属性递增
     *
     * @param key     外部key
     * @param hashKey 内部key
     * @param delta   递增条件
     * @return 返回递增后的数据
     */
    Long hIncr(String key, String hashKey, Long delta);

    /**
     * Hash结构中属性递减
     *
     * @param key     外部key
     * @param hashKey 内部key
     * @param delta   递增条件
     * @return 返回递减后的数据
     */
    Long hDecr(String key, String hashKey, Long delta);

    /**
     * 获取Set结构
     *
     * @param key key
     * @return 返回set集合
     */
    Set<Object> sMembers(String key);

    /**
     * 向Set结构中添加属性
     *
     * @param key    key
     * @param values value集
     * @return 返回增加数量
     */
    Long sAdd(String key, Object... values);

    /**
     * 向Set结构中添加属性
     *
     * @param key    key
     * @param time   过期时间
     * @param values 值集合
     * @return 返回添加的数量
     */
    Long sAdd(String key, long time, Object... values);

    /**
     * 是否为Set中的属性
     *
     * @param key   key
     * @param value value
     * @return 返回是否存在
     */
    Boolean sIsMember(String key, Object value);

    /**
     * 获取Set结构的长度
     *
     * @param key key
     * @return 返回长度
     */
    Long sSize(String key);

    /**
     * 删除Set结构中的属性
     *
     * @param key    key
     * @param values value集合
     * @return 删除掉的数据量
     */
    Long sRemove(String key, Object... values);

    /**
     * 获取List结构中的属性
     *
     * @param key   key
     * @param start 开始
     * @param end   结束
     * @return 返回查询的集合
     */
    List<Object> lRange(String key, long start, long end);

    /**
     * 获取List结构的长度
     *
     * @param key key
     * @return 长度
     */
    Long lSize(String key);

    /**
     * 根据索引获取List中的属性
     *
     * @param key   key
     * @param index 索引
     * @return 对象
     */
    Object lIndex(String key, long index);

    /**
     * 向List结构中添加属性
     *
     * @param key   key
     * @param value value
     * @return 增加后的长度
     */
    Long lPush(String key, Object value);

    /**
     * 向List结构中添加属性
     *
     * @param key   key
     * @param value value
     * @param time  过期时间
     * @return 增加后的长度
     */
    Long lPush(String key, Object value, long time);

    /**
     * 向List结构中批量添加属性
     *
     * @param key    key
     * @param values value 集合
     * @return 增加后的长度
     */
    Long lPushAll(String key, Object... values);

    /**
     * 向List结构中批量添加属性
     *
     * @param key    key
     * @param time   过期时间
     * @param values value集合
     * @return 增加后的长度
     */
    Long lPushAll(String key, Long time, Object... values);

    /**
     * 从List结构中移除属性
     *
     * @param key   key
     * @param count 总量
     * @param value value
     * @return 返回删除后的长度
     */
    Long lRemove(String key, long count, Object value);

    /**
     * 向bitmap中新增值
     *
     * @param key    key
     * @param offset 偏移量
     * @param b      状态
     * @return 结果
     */
    Boolean bitAdd(String key, int offset, boolean b);

    /**
     * 从bitmap中获取偏移量的值
     *
     * @param key    key
     * @param offset 偏移量
     * @return 结果
     */
    Boolean bitGet(String key, int offset);

    /**
     * 获取bitmap的key值总和
     *
     * @param key key
     * @return 总和
     */
    Long bitCount(String key);

    /**
     * 获取bitmap范围值
     *
     * @param key    key
     * @param limit  范围
     * @param offset 开始偏移量
     * @return long类型集合
     */
    List<Long> bitField(String key, int limit, int offset);

    /**
     * 获取所有bitmap
     *
     * @param key key
     * @return 以二进制字节数组返回
     */
    byte[] bitGetAll(String key);

    /**
     * 增加坐标
     *
     * @param key  key
     * @param x    x
     * @param y    y
     * @param name 地点名称
     * @return 返回结果
     */
    Long geoAdd(String key, Double x, Double y, String name);

    /**
     * 根据城市名称获取坐标集合
     *
     * @param key   key
     * @param place 地点
     * @return 坐标集合
     */
    List<Point> geoGetPointList(String key, Object... place);

    /**
     * 计算两个城市之间的距离
     *
     * @param key      key
     * @param placeOne 地点1
     * @param placeTow 地点2
     * @return 返回距离
     */
    Distance geoCalculationDistance(String key, String placeOne, String placeTow);

    /**
     * 获取附该地点附近的其他地点
     *
     * @param key      key
     * @param place    地点
     * @param distance 附近的范围
     * @param limit    查几条
     * @param sort     排序规则
     * @return 返回附近的地点集合
     */
    GeoResults<RedisGeoCommands.GeoLocation<Object>> geoNearByPlace(String key, String place, Distance distance, long limit, Sort.Direction sort);

    /**
     * 获取地点的hash
     *
     * @param key   key
     * @param place 地点
     * @return 返回集合
     */
    List<String> geoGetHash(String key, String... place);
}



================================================
FILE: src/main/java/com/blog/service/SitemapService.java
================================================
package com.blog.service;

import com.blog.entity.Blog;
import com.blog.dao.BlogDao;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * 站点地图服务
 * 自动生成 sitemap.xml 用于 SEO
 * @author tangredtea
 */
@Slf4j
@Service
public class SitemapService {

    @Resource
    private BlogDao blogDao;
    
    private static final String SITE_URL = "https://your-domain.com";
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    
    /**
     * 生成 XML 格式的站点地图
     */
    public String generateSitemap() {
        StringBuilder sitemap = new StringBuilder();
        sitemap.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        sitemap.append("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n");
        
        // 首页
        addUrl(sitemap, "/", "1.0", "daily");
        
        // 分类页
        addUrl(sitemap, "/types", "0.8", "weekly");
        
        // 标签页
        addUrl(sitemap, "/tags", "0.8", "weekly");
        
        // 关于页
        addUrl(sitemap, "/about", "0.5", "monthly");
        
        // 留言页
        addUrl(sitemap, "/message", "0.6", "weekly");
        
        // 所有已发布的文章
        List<Blog> blogs = blogDao.getAllPublishedBlogs();
        for (Blog blog : blogs) {
            String lastmod = blog.getUpdateTime() != null ? 
                DATE_FORMAT.format(blog.getUpdateTime().toInstant()) :
                DATE_FORMAT.format(LocalDateTime.now());
            addUrl(sitemap, "/blog/" + blog.getId(), "0.9", "weekly", lastmod);
        }
        
        sitemap.append("</urlset>");
        return sitemap.toString();
    }
    
    private void addUrl(StringBuilder sb, String loc, String priority, String changefreq) {
        addUrl(sb, loc, priority, changefreq, DATE_FORMAT.format(LocalDateTime.now()));
    }
    
    private void addUrl(StringBuilder sb, String loc, String priority, String changefreq, String lastmod) {
        sb.append("  <url>\n");
        sb.append("    <loc>").append(SITE_URL).append(loc).append("</loc>\n");
        sb.append("    <lastmod>").append(lastmod).append("</lastmod>\n");
        sb.append("    <changefreq>").append(changefreq).append("</changefreq>\n");
        sb.append("    <priority>").append(priority).append("</priority>\n");
        sb.append("  </url>\n");
    }
}


================================================
FILE: src/main/java/com/blog/service/SmartSearchService.java
================================================
package com.blog.service;

import com.blog.dao.BlogDao;
import com.blog.entity.Blog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 智能搜索服务
 * 提供语义化搜索和相关推荐
 * @author tangredtea
 */
@Slf4j
@Service
public class SmartSearchService {

    @Resource
    private BlogDao blogDao;

    /** Simple in-memory cache for search suggestions to avoid repeated DB queries */
    private volatile List<Blog> publishedBlogsCache;
    private volatile long cacheTimestamp;
    private static final long CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes

    private List<Blog> getCachedPublishedBlogs() {
        long now = System.currentTimeMillis();
        if (publishedBlogsCache == null || now - cacheTimestamp > CACHE_TTL_MS) {
            publishedBlogsCache = blogDao.getAllPublishedBlogs();
            cacheTimestamp = now;
        }
        return publishedBlogsCache;
    }

    /**
     * 提取关键词
     * @param text 文本内容
     * @return 关键词列表
     */
    public List<String> extractKeywords(String text) {
        // 简单的中文分词实现
        // 实际项目中可以使用 HanLP、jieba 等分词库
        Set<String> stopWords = new HashSet<>(Arrays.asList(
            "的", "了", "在", "是", "我", "有", "和", "就", "不", "人", "都", "一", "一个", "上", "也",
            "很", "到", "说", "要", "去", "你", "会", "着", "没有", "看", "好", "自己", "这"
        ));
        
        // 去除 HTML 标签
        String cleanText = text.replaceAll("<[^>]+>", "");
        
        // 按非中文字符分割
        String[] words = cleanText.split("[^\\u4e00-\\u9fa5a-zA-Z0-9]+");
        
        Map<String, Integer> wordFreq = new HashMap<>();
        for (String word : words) {
            word = word.toLowerCase().trim();
            if (word.length() >= 2 && !stopWords.contains(word)) {
                wordFreq.merge(word, 1, Integer::sum);
            }
        }
        
        // 按频率排序,取前 10
        return wordFreq.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
                .limit(10)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }
    
    /**
     * 计算文章相似度
     * @param blog1 文章1
     * @param blog2 文章2
     * @return 相似度分数 (0-1)
     */
    public double calculateSimilarity(Blog blog1, Blog blog2) {
        // 基于标签和分类计算相似度
        double score = 0.0;
        
        // 同分类 +0.3
        if (blog1.getTypeId() != null && blog1.getTypeId().equals(blog2.getTypeId())) {
            score += 0.3;
        }
        
        // 标签重叠
        if (blog1.getTagIds() != null && blog2.getTagIds() != null) {
            Set<String> tags1 = new HashSet<>(Arrays.asList(blog1.getTagIds().split(",")));
            Set<String> tags2 = new HashSet<>(Arrays.asList(blog2.getTagIds().split(",")));
            
            Set<String> intersection = new HashSet<>(tags1);
            intersection.retainAll(tags2);
            
            if (!tags1.isEmpty() || !tags2.isEmpty()) {
                score += 0.7 * ((double) intersection.size() / 
                    Math.max(tags1.size(), tags2.size()));
            }
        }
        
        return score;
    }
    
    /**
     * 获取相关文章推荐
     * @param blogId 当前文章ID
     * @param limit 返回数量
     * @return 相关文章列表
     */
    public List<Blog> getRelatedBlogs(Long blogId, int limit) {
        Blog currentBlog = blogDao.getBlog(blogId);
        if (currentBlog == null) {
            return Collections.emptyList();
        }
        
        List<Blog> allBlogs = getCachedPublishedBlogs();
        
        return allBlogs.stream()
                .filter(b -> !b.getId().equals(blogId))
                .map(b -> new AbstractMap.SimpleEntry<>(b, calculateSimilarity(currentBlog, b)))
                .filter(e -> e.getValue() > 0.1)  // 过滤低相似度
                .sorted(Map.Entry.<Blog, Double>comparingByValue().reversed())
                .limit(limit)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }
    
    /**
     * 智能搜索建议
     * @param query 搜索词
     * @return 建议列表
     */
    public List<String> getSearchSuggestions(String query) {
        if (query == null || query.trim().isEmpty()) {
            return Collections.emptyList();
        }

        List<Blog> blogs = getCachedPublishedBlogs();
        String lowerQuery = query.toLowerCase();

        // Match by title first, then by description
        List<String> titleMatches = blogs.stream()
                .filter(b -> b.getTitle() != null &&
                       b.getTitle().toLowerCase().contains(lowerQuery))
                .map(Blog::getTitle)
                .collect(Collectors.toList());

        List<String> descMatches = blogs.stream()
                .filter(b -> b.getTitle() != null &&
                       !b.getTitle().toLowerCase().contains(lowerQuery) &&
                       b.getDescription() != null &&
                       b.getDescription().toLowerCase().contains(lowerQuery))
                .map(Blog::getTitle)
                .collect(Collectors.toList());

        List<String> result = new ArrayList<>(titleMatches);
        result.addAll(descMatches);

        return result.stream().limit(6).collect(Collectors.toList());
    }
}


================================================
FILE: src/main/java/com/blog/service/TagService.java
================================================
package com.blog.service;

import com.blog.entity.Tag;

import java.util.List;

/**
 * @author tangredtea
 */
public interface TagService {
    /**
     * 保存标签
     * @param tag 标签
     * @return 状态值
     */
    int saveTag(Tag tag);

    /**
     * 得到标签
     * @param id 标签id
     * @return 标签
     */
    Tag getTag(Integer id);

    /**
     * 通过名字得到标签
     * @param  name 标签名
     * @return 标签
     */
    Tag getTagByName(String name);

    /**
     * 得到所有的标签名
     * @return 标签列表
     */
    List<Tag> getAllTag();

    /**
     * 首页展示博客标签
     * @return 标签列表
     */
    List<Tag> getBlogTag();

    /**
     * 从字符串中获取tag集合
     * @param text 标签名
     * @return 标签列表
     */
    List<Tag> getTagByString(String text);

    /**
     * 升级标签
     * @param tag 标签
     * @return 状态值
     */
    int updateTag(Tag tag);

    /**
     * 删除标签
     * @param id 标签id
     * @return 状态值
     */
    int deleteTag(Integer id);

    /**
     * 得到标签数量
     * @return 数量
     */
    int countTag();
}


================================================
FILE: src/main/java/com/blog/service/TypeService.java
================================================
package com.blog.service;

import com.blog.entity.Type;

import java.util.List;

/**
 * @author tangredtea
 */
public interface TypeService {
    /**
     * 保存分类
     * @param type 分类
     * @return 状态值
     */
    int saveType(Type type);

    /**
     * 得到分类名
     * @param id 分类id
     * @return 分类
     */
    Type getType(Integer id);

    /**
     * 通过名字得到分类名
     * @param name 分类名
     * @return 分类
     */
    Type getTypeByName(String name);

    /**
     * 得到所有分类
     * @return 分类列表
     */
    List<Type> getAllType();

    /**
     * 首页右侧展示type对应的博客数量
     * @return 分类列表
     */
    List<Type> getBlogType();

    /**
     * 更新分类
     * @param type 分类
     * @return 状态值
     */
    int updateType(Type type);

    /**
     * 删除分类
     * @param id 分类id
     * @return 状态值
     */
    int deleteType(Integer id);

    /**
     * 得到分类数量
     * @return 数量
     */
    int countType();
}


================================================
FILE: src/main/java/com/blog/service/UserService.java
================================================
package com.blog.service;

import com.blog.entity.User;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * @author tangredtea
 */
public interface UserService {

    /**
     * 登录
     * @param username 账号
     * @param password 密码
     * @return user
     */
    User checkUser(@Param("username") String username, @Param("password") String password);

    /**
     * 通过用户ID获取用户信息
     * @param id   主键
     * @return user
     */
    User getUserInfoById(Integer id);

    /**
     * 更改用户信息
     * @param user  user对象
     * @return 状态值
     */
    int updateUser(User user);

    /**
     * 更改用户信息
     * @param user  user对象
     * @return 状态值
     */
    int saveUser(User user);

    /**
     * 得到所有用户
     * @return 用户
     */
    List<User> getUsers();

    /**
     * 删除用户
     * @param id 用户id
     * @return 状态值
     */
    int deleteUser(Integer id);

    /**
     * 查询用户名数量
     * @param name
     * @return 数量
     */
    int getUserInfoByUsername(String name);

    /**
     * 得到用户数量
     * @return 数量
     */
    int countUser();
}


================================================
FILE: src/main/java/com/blog/service/impl/BlogServiceImpl.java
================================================
package com.blog.service.impl;

import com.blog.dao.BlogDao;
import com.blog.config.RedisKey;
import com.blog.exception.NotFoundException;
import com.blog.entity.Blog;
import com.blog.entity.BlogAndTag;
import com.blog.entity.Tag;
import com.blog.service.BlogService;
import com.blog.service.RedisService;
import com.blog.util.MarkdownUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;

/**
 * @author tangredtea
 */
@Service
public class BlogServiceImpl implements BlogService {

    @Resource
    RedisService cache;

    @Resource
    BlogDao blogDao;

    @Override
    public Blog getBlog(Long id) {
        return blogDao.getBlog(id);
    }

    @Override
    public Blog getDetailedBlog(Long id) {
        Blog blog = Optional.ofNullable(blogDao.getDetailedBlog(id))
                            .orElseThrow(() -> new NotFoundException("该博客不存在"));
        String content = blog.getContent();
        //将Markdown格式转换成html
        blog.setContent(MarkdownUtils.markdownToHtmlExtensions(content));
        return blog;
    }

    @Override
    public List<Blog> getAllBlog() {
        return blogDao.getAllBlog();
    }

    @Override
    public List<Blog> getByTypeId(Integer typeId) {
        return blogDao.getByTypeId(typeId);
    }

    @Override
    public List<Blog> getByTagId(Integer tagId) {
        return blogDao.getByTagId(tagId);
    }

    @Override
    public List<Blog> getIndexBlog() {
        return blogDao.getIndexBlog();
    }

    @Override
    public List<Blog> getAllRecommendBlog() {
        return blogDao.getAllRecommendBlog();
    }

    @Override
    public List<Blog> getSearchBlog(String query) {
        return blogDao.getSearchBlog(query);
    }

    @Override
    public Map<String, List<Blog>> archiveBlog() {
        List<String> years = blogDao.findGroupYear();
        //set去掉重复的年份
        Set<String> set = new HashSet<>(years);
        Map<String, List<Blog>> map = new HashMap<>(8);
        set.forEach(year -> map.put(year, blogDao.findByYear(year)));
        return map;
    }

    @Override
    public int countBlog() {
        return blogDao.getCount();
    }

    @Override
    public int getTotalViews() {
        return blogDao.getViews();
    }

    @Override
    public int getAvgViews() {
        return blogDao.getAvgViews();
    }

    @Override
    public List<Blog> searchAllBlog(Blog blog) {
        return blogDao.searchAllBlog(blog);
    }

    @Override
    public List<Blog> getHotBlog() {
        return blogDao.getHotBlog();
    }

    /**
     * 状态值
     * @param blog 博文
     * @return 保存博文
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int saveBlog(Blog blog) {
        final Date now = new Date();
        blog.setCreateTime(now);
        blog.setUpdateTime(now);
        blog.setViews(0);
        blogDao.saveBlog(blog);
        Long id = blog.getId();
        blog.getTags().forEach(tag -> {
            BlogAndTag blogAndTag = new BlogAndTag(tag.getId(), id);
            blogDao.saveBlogAndTag(blogAndTag);
        });
        return 1;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int updateBlog(Blog blog) {
        blog.setUpdateTime(new Date());
        // 先删除旧的标签关联,再插入新的
        blogDao.deleteBlogAndTagByBlogId(blog.getId());
        blog.getTags().forEach(tag -> {
            BlogAndTag blogAndTag = new BlogAndTag(tag.getId(), blog.getId());
            blogDao.saveBlogAndTag(blogAndTag);
        });
        if (cache.hHasKey(RedisKey.ARTICLE, String.valueOf(blog.getId()))){
            cache.hSet(RedisKey.ARTICLE, String.valueOf(blog.getId()), blog);
        }
        return blogDao.updateBlog(blog);
    }

    @Override
    public int deleteBlog(Long id) {
        //如果缓存中有这个键值的话
        if (cache.hHasKey(RedisKey.ARTICLE_VIEWS, String.valueOf(id))){
            cache.hDel(RedisKey.ARTICLE_VIEWS, String.valueOf(id));
        }
        if (cache.hHasKey(RedisKey.ARTICLE, String.valueOf(id))){
            cache.hDel(RedisKey.ARTICLE, String.valueOf(id));
        }
        return blogDao.deleteBlog(id);
    }

}


================================================
FILE: src/main/java/com/blog/service/impl/FriendLinkServiceImpl.java
================================================
package com.blog.service.impl;

import com.blog.dao.FriendLinkDao;
import com.blog.entity.FriendLink;
import com.blog.service.FriendLinkService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author tangredtea
 */
@Service
public class FriendLinkServiceImpl implements FriendLinkService {

    @Resource
    private FriendLinkDao friendLinkDao;

    @Override
    public List<FriendLink> listFriendLink() {
        return friendLinkDao.listFriendLink();
    }

    @Override
    public int saveFriendLink(FriendLink friendLink) {
        return friendLinkDao.saveFriendLink(friendLink);
    }

    @Override
    public FriendLink getFriendLink(Integer id) {
        return friendLinkDao.getFriendLink(id);
    }

    @Override
    public FriendLink getFriendLinkByBlogAddress(String blogAddress) {
        return friendLinkDao.getFriendLinkByBlogAddress(blogAddress);
    }

    @Override
    public int updateFriendLink(FriendLink friendLink) {
        return friendLinkDao.updateFriendLink(friendLink);
    }

    @Override
    public void deleteFriendLink(Integer id) {
        friendLinkDao.deleteFriendLink(id);
    }

    @Override
    public int countFriendLink() {
        return friendLinkDao.getCount();
    }

}


================================================
FILE: src/main/java/com/blog/service/impl/MessageServiceImpl.java
================================================
package com.blog.service.impl;

import com.blog.dao.MessageDao;
import com.blog.entity.Message;
import com.blog.service.MessageService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author tangredtea
 */
@Service
public class MessageServiceImpl implements MessageService {

    @Resource
    private MessageDao messageDao;

    /**
     * 首页推荐评论
     * @return 留言列表
     */
    @Override
    public  List<Message> findByIndexParentId(){
        return messageDao.findByParentIdNull(Long.parseLong("-1"));
    }

    /**
     * 列出留言
     * @return 留言列表
     */
    @Override
    public List<Message> listMessage() {
        //查询出父节点
        List<Message> messages = messageDao.findByParentIdNull(Long.parseLong("-1"));
        for(Message message : messages){
            Long id = message.getId();
            String parentNickname1 = message.getNickname();
            List<Message> childMessages = messageDao.findByParentIdNotNull(id);
            //查询出子留言
            List<Message> tempReplies = new ArrayList<>();
            combineChildren(tempReplies, childMessages, parentNickname1);
            message.setReplyMessages(tempReplies);
        }
        return messages;
    }


    /**
     * 查询子留言
     * @param tempReplies 存放子留言的集合
     * @param childMessages 子留言
     * @param parentNickname1 父留言名称
     */
    private void combineChildren(List<Message> tempReplies, List<Message> childMessages, String parentNickname1) {
        if(!childMessages.isEmpty()){
            for(Message childMessage : childMessages){
                String parentNickname = childMessage.getNickname();
                childMessage.setParentNickname(parentNickname1);
                tempReplies.add(childMessage);
                Long childId = childMessage.getId();
                recursively(tempReplies, childId, parentNickname);
            }
        }
    }

    /**
     * 循环迭代找出子集回复
     * @param tempReplies 存放子留言的集合
     * @param childId 子集id
     * @param parentNickname1 父名称
     */
    private void recursively(List<Message> tempReplies, Long childId, String parentNickname1) {
        List<Message> replayMessages = messageDao.findByReplayId(childId);
        if(!replayMessages.isEmpty()){
            for(Message replayMessage : replayMessages){
                String parentNickname = replayMessage.getNickname();
                replayMessage.setParentNickname(parentNickname1);
                Long replayId = replayMessage.getId();
                tempReplies.add(replayMessage);
                recursively(tempReplies, replayId, parentNickname);
            }
        }
    }

    /**
     * 保存留言
     * @param message 留言
     * @return 状态
     */
    @Override
    public int saveMessage(Message message) {
        message.setCreateTime(new Date());
        return messageDao.saveMessage(message);
    }

    /**
     *  删除留言
     * @param id 留言id
     */
    @Override
    public void deleteMessage(Long id) {
        messageDao.deleteMessage(id);
    }

    @Override
    public int countMessage() {
        return messageDao.getCount();
    }
}


================================================
FILE: src/main/java/com/blog/service/impl/RedisServiceImpl.java
================================================
package com.blog.service.impl;
import com.blog.service.RedisService;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;


/**
 * redis操作
 *
 *
 * @author tangredtea*/
@Service
public class RedisServiceImpl implements RedisService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void set(String key, Object value, long time) {
        redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
    }

    @Override
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    @Override
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    @Override
    public Boolean del(String key) {
        return redisTemplate.delete(key);
    }

    @Override
    public Long del(List<String> keys) {
        return redisTemplate.delete(keys);
    }

    @Override
    public Boolean expire(String key, long time) {
        return redisTemplate.expire(key, time, TimeUnit.SECONDS);
    }

    @Override
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    @Override
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    @Override
    public Long incr(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    @Override
    public Long decr(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    @Override
    public Object hGet(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    @Override
    public Boolean hSet(String key, String hashKey, Object value, long time) {
        redisTemplate.opsForHash().put(key, hashKey, value);
        return expire(key, time);
    }

    @Override
    public void hSet(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    @Override
    public Map hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    @Override
    public Boolean hSetAll(String key, Map<String, Object> map, long time) {
        redisTemplate.opsForHash().putAll(key, map);
        return expire(key, time);
    }

    @Override
    public void hSetAll(String key, Map<String, ?> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    @Override
    public void hDel(String key, Object... hashKey) {
        redisTemplate.opsForHash().delete(key, hashKey);
    }

    @Override
    public Boolean hHasKey(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    @Override
    public Long hIncr(String key, String hashKey, Long delta) {
        return redisTemplate.opsForHash().increment(key, hashKey, delta);
    }

    @Override
    public Long hDecr(String key, String hashKey, Long delta) {
        return redisTemplate.opsForHash().increment(key, hashKey, -delta);
    }

    @Override
    public Set<Object> sMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    @Override
    public Long sAdd(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    @Override
    public Long sAdd(String key, long time, Object... values) {
        Long count = redisTemplate.opsForSet().add(key, values);
        expire(key, time);
        return count;
    }

    @Override
    public Boolean sIsMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    @Override
    public Long sSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    @Override
    public Long sRemove(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }

    @Override
    public List<Object> lRange(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    @Override
    public Long lSize(String key) {
        return redisTemplate.opsForList().size(key);
    }

    @Override
    public Object lIndex(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }

    @Override
    public Long lPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    @Override
    public Long lPush(String key, Object value, long time) {
        Long index = redisTemplate.opsForList().rightPush(key, value);
        expire(key, time);
        return index;
    }

    @Override
    public Long lPushAll(String key, Object... values) {
        return redisTemplate.opsForList().rightPushAll(key, values);
    }

    @Override
    public Long lPushAll(String key, Long time, Object... values) {
        Long count = redisTemplate.opsForList().rightPushAll(key, values);
        expire(key, time);
        return count;
    }

    @Override
    public Long lRemove(String key, long count, Object value) {
        return redisTemplate.opsForList().remove(key, count, value);
    }

    @Override
    public Boolean bitAdd(String key, int offset, boolean b) {
        return redisTemplate.opsForValue().setBit(key, offset, b);
    }

    @Override
    public Boolean bitGet(String key, int offset) {
        return redisTemplate.opsForValue().getBit(key, offset);
    }

    @Override
    public Long bitCount(String key) {
        return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
    }

    @Override
    public List<Long> bitField(String key, int limit, int offset) {
        return redisTemplate.execute((RedisCallback<List<Long>>) con ->
                con.bitField(key.getBytes(),
                        BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(limit)).valueAt(offset)));
    }

    @Override
    public byte[] bitGetAll(String key) {
        return redisTemplate.execute((RedisCallback<byte[]>) con -> con.get(key.getBytes()));
    }

    @Override
    public Long geoAdd(String key, Double x, Double y, String name) {
        return redisTemplate.opsForGeo().add(key, new Point(x, y), name);
    }

    @Override
    public List<Point> geoGetPointList(String key, Object... place) {
        return redisTemplate.opsForGeo().position(key, place);
    }

    @Override
    public Distance geoCalculationDistance(String key, String placeOne, String placeTow) {
        return redisTemplate.opsForGeo()
                .distance(key, placeOne, placeTow, RedisGeoCommands.DistanceUnit.KILOMETERS);
    }

    @Override
    public GeoResults<RedisGeoCommands.GeoLocation<Object>> geoNearByPlace(String key, String place, Distance distance, long limit, Sort.Direction sort) {
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates();
        // 判断排序方式
        if (Sort.Direction.ASC == sort) {
            args.sortAscending();
        } else {
            args.sortDescending();
        }
        args.limit(limit);
        return redisTemplate.opsForGeo()
                .radius(key, place, distance, args);
    }

    @Override
    public List<String> geoGetHash(String key, String... place) {
        return redisTemplate.opsForGeo()
                .hash(key, place);
    }

}


================================================
FILE: src/main/java/com/blog/service/impl/TagServiceImpl.java
================================================
package com.blog.service.impl;

import com.blog.dao.TagDao;
import com.blog.entity.Tag;
import com.blog.service.TagService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * @author tangredtea
 */
@Service
public class TagServiceImpl implements TagService {

    @Resource
    TagDao tagDao;

    @Override
    public int saveTag(Tag tag) {
        return tagDao.saveTag(tag);
    }

    @Override
    public Tag getTag(Integer id) {
        return tagDao.getTag(id);
    }

    @Override
    public Tag getTagByName(String name) {
        return tagDao.getTagByName(name);
    }

    @Override
    public List<Tag> getAllTag() {
        return tagDao.getAllTag();
    }

    @Override
    public List<Tag> getBlogTag() {
        return tagDao.getBlogTag();
    }

    /**
     * 从tagIds字符串中获取id,根据id获取tag集合
     * @param text 标签名
     * @return 标签列表
     */
    @Override
    public List<Tag> getTagByString(String text) {
        List<Tag> tags = new ArrayList<>();
        List<Integer> nums = convertToList(text);
        for (Integer num : nums) {
            tags.add(tagDao.getTag(num));
        }
        return tags;
    }

    /**
     * 把前端的tagIds字符串转换为list集合
     * @param ids id
     * @return id列表
     */
    private List<Integer> convertToList(String ids) {
        List<Integer> list = new ArrayList<>();
        if (!"".equals(ids) && ids != null) {
            String[] idArrays = ids.split(",");
            for (String idArray : idArrays) {
                list.add(Integer.valueOf(idArray));
            }
        }
        return list;
    }

    @Override
    public int updateTag(Tag tag) {
        return tagDao.updateTag(tag);
    }

    @Override
    public int deleteTag(Integer id) {
        return tagDao.deleteTag(id);
    }

    @Override
    public int countTag() {
        return tagDao.getCount();
    }
}


================================================
FILE: src/main/java/com/blog/service/impl/TypeServiceImpl.java
================================================
package com.blog.service.impl;

import com.blog.dao.TypeDao;
import com.blog.entity.Type;
import com.blog.service.RedisService;
import com.blog.service.TypeService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author tangredtea
 */
@Service
public class TypeServiceImpl implements TypeService {

    @Resource
    RedisService cache;

    @Resource
    private TypeDao typeDao;

    @Override
    public int saveType(Type type) {
        return typeDao.saveType(type);
    }

    @Override
    public Type getType(Integer id) {
        return typeDao.getType(id);
    }

    @Override
    public Type getTypeByName(String name) {
        return typeDao.getTypeByName(name);
    }

    @Override
    public List<Type> getAllType() {
        return typeDao.getAllType();
    }


    @Override
    public List<Type> getBlogType() {
        return typeDao.getBlogType();
    }

    @Override
    public int updateType(Type type) {
        return typeDao.updateType(type);
    }

    @Override
    public int deleteType(Integer id) {
        return typeDao.deleteType(id);
    }

    @Override
    public int countType() {
        return typeDao.getCount();
    }
}


================================================
FILE: src/main/java/com/blog/service/impl/UserServiceImpl.java
================================================
package com.blog.service.impl;

import com.blog.dao.UserDao;
import com.blog.entity.User;
import com.blog.service.UserService;
import com.blog.util.PasswordUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author tangredtea
 */
@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao userDao;


    @Override
    public User checkUser(String username, String password) {
        User user = userDao.queryByUsername(username);
        if (user != null && PasswordUtils.matches(password, user.getPassword())) {
            return user;
        }
        return null;
    }

    @Override
    public User getUserInfoById(Integer id) {
        return userDao.getUserInfoById(id);
    }

    @Override
    public int updateUser(User user) {
        return userDao.updateUser(user);
    }

    @Override
    public int saveUser(User user) {
        return userDao.saveUser(user);
    }

    @Override
    public List<User> getUsers() {
        return userDao.getAllUser();
    }

    @Override
    public int deleteUser(Integer id) {
        return userDao.deleteUser(id);
    }

    @Override
    public int getUserInfoByUsername(String name) {
        return userDao.getUserInfoByUsername(name);
    }

    @Override
    public int countUser() {
        return userDao.getCount();
    }

}


================================================
FILE: src/main/java/com/blog/util/CommonResult.java
================================================
package com.blog.util;

import lombok.Data;

/**
 * 公共逻辑返回类
 * @author tangredtea
 */
@Data
public class CommonResult<T> {
    private Integer code;
    private String msg;
    private T data;

    public CommonResult(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static <T> CommonResult<T> success(T t) {
        return new CommonResult<>(200, "操作成功", t);
    }

    public static <T> CommonResult<T> error(T t) {
        return new CommonResult<>(500, "操作失败", t);
    }

}



================================================
FILE: src/main/java/com/blog/util/MD5Utils.java
================================================
package com.blog.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * @author tangredtea
 */
public class MD5Utils {

    /**
     * MD5加密类
     * @param str 要加密的字符串
     * @return    加密后的字符串
     */
    public static String code(String str){
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(str.getBytes());
            byte[]byteDigest = md.digest();
            int i;
            StringBuilder buf = new StringBuilder();
            for (byte b : byteDigest) {
                i = b;
                if (i < 0) {
                    i += 256;
                }
                if (i < 16) {
                    buf.append("0");
                }
                buf.append(Integer.toHexString(i));
            }
            //32位加密
            return buf.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }

    }
}


================================================
FILE: src/main/java/com/blog/util/MarkdownUtils.java
================================================
package com.blog.util;

import org.commonmark.Extension;
import org.commonmark.ext.gfm.tables.TableBlock;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.ext.heading.anchor.HeadingAnchorExtension;
import org.commonmark.node.Link;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.AttributeProvider;
import org.commonmark.renderer.html.HtmlRenderer;

import java.util.*;


/**
 * @author tangredtea
 */
public class MarkdownUtils {

    /**
     * markdown格式转换成HTML格式
     * @param markdown 文本
     * @return 渲染后的文本
     */
    public static String markdownToHtml(String markdown) {
        Parser parser = Parser.builder().build();
        Node document = parser.parse(markdown);
        HtmlRenderer renderer = HtmlRenderer.builder().build();
        return renderer.render(document);
    }

    /**
     * 增加扩展[标题锚点,表格生成]
     * Markdown转换成HTML
     * @param markdown 文本
     * @return 渲染后的文本
     */
    public static String markdownToHtmlExtensions(String markdown) {
        //h标题生成id
        Set<Extension> headingAnchorExtensions = Collections.singleton(HeadingAnchorExtension.create());
        //转换table的HTML
        List<Extension> tableExtension = Collections.singletonList(TablesExtension.create());
        Parser parser = Parser.builder()
                .extensions(tableExtension)
                .build();
        Node document = parser.parse(markdown);
        HtmlRenderer renderer = HtmlRenderer.builder()
                .extensions(headingAnchorExtensions)
                .extensions(tableExtension)
                .attributeProviderFactory(context -> new CustomAttributeProvider())
                .build();
        return renderer.render(document);
    }

    /**
     * 处理标签的属性
     */
    static class CustomAttributeProvider implements AttributeProvider {
        @Override
        public void setAttributes(Node node, String tagName, Map<String, String> attributes) {
            //改变a标签的target属性为_blank
            if (node instanceof Link) {
                attributes.put("target", "_blank");
            }
            if (node instanceof TableBlock) {
                attributes.put("class", "ui celled table");
            }
        }
    }


}


================================================
FILE: src/main/java/com/blog/util/PasswordUtils.java
================================================
package com.blog.util;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * 密码加密工具类
 * 使用 BCrypt 算法,比 MD5 更安全
 * @author tangredtea
 */
public class PasswordUtils {

    private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

    /**
     * 加密密码
     * @param rawPassword 原始密码
     * @return 加密后的密码
     */
    public static String encode(String rawPassword) {
        return encoder.encode(rawPassword);
    }

    /**
     * 验证密码
     * @param rawPassword 原始密码
     * @param encodedPassword 加密后的密码
     * @return 是否匹配
     */
    public static boolean matches(String rawPassword, String encodedPassword) {
        return encoder.matches(rawPassword, encodedPassword);
    }
}


================================================
FILE: src/main/java/com/blog/util/PropertiesUtil.java
================================================
package com.blog.util;


import com.blog.config.SettingsConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ResourceUtils;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Properties;

/**
 * @author tangredtea
 */
@Slf4j
public class PropertiesUtil {

    private static String path;

    static {
        try {
            path = ResourceUtils.getURL("classpath:").getPath()+"messages.properties";
        } catch (FileNotFoundException e) {
            log.error("messages.properties not found", e);
        }
    }

    public static String getValueByKey(String filePath, String key) {
        Properties pps = new Properties();
        try (InputStream in = new BufferedInputStream(new FileInputStream(filePath))) {
            pps.load(in);
            return pps.getProperty(key);
        } catch (IOException e) {
            log.error("Failed to read property: {}", key, e);
            return null;
        }
    }

    public static void writeProperties(String pKey, String pValue) throws IOException {
        Properties pps = new Properties();
        try (InputStream in = new FileInputStream(path)) {
            pps.load(in);
        }
        pps.setProperty(pKey, pValue);
        try (OutputStream out = new FileOutputStream(path)) {
            pps.store(out, "Update " + pKey + " name");
        }
    }

    public static void write(Class<?> t, SettingsConfig settings) throws IllegalAccessException, IOException {
        Field[] declaredFields = t.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            writeProperties(declaredField.getName(), (String) declaredField.get(settings));
        }
    }
}


================================================
FILE: src/main/java/com/blog/util/RedisUtil.java
================================================
package com.blog.util;

import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author tangredtea

 */
@Component
public class RedisUtil {

    @Resource
    private RedisTemplate redisTemplate;

    private static final double size = Math.pow(2, 32);


    public boolean setBit(String key, long offset, boolean isShow) {
        boolean result = false;
        try {
            ValueOperations operations = redisTemplate.opsForValue();
            operations.setBit(key, offset, isShow);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    public boolean getBit(String key, long offset) {
        boolean result = false;
        try {
            ValueOperations operations = redisTemplate.opsForValue();
            result = operations.getBit(key, offset);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 写入缓存设置时效时间
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 批量删除对应的value
     *
     * @param keys
     */
    public void remove(final String... keys) {
        for (String key : keys) {
            remove(key);
        }
    }

    /**
     * 删除对应的value
     *
     * @param key
     */
    public void remove(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }

    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }


    public Object get(final String key) {
        Object result;
        ValueOperations operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    public void hmSet(String key, Object hashKey, Object value) {
        HashOperations hash = redisTemplate.opsForHash();
        hash.put(key, hashKey, value);
    }

    public Object hmGet(String key, Object hashKey) {
        HashOperations hash = redisTemplate.opsForHash();
        return hash.get(key, hashKey);
    }


    public void lPush(String k, Object v) {
        ListOperations list = redisTemplate.opsForList();
        list.rightPush(k, v);
    }


    public List lRange(String k, long l, long l1) {
        ListOperations list = redisTemplate.opsForList();
        return list.range(k, l, l1);
    }


    public void add(String key, Object value) {
        SetOperations set = redisTemplate.opsForSet();
        set.add(key, value);
    }


    public Set setMembers(String key) {
        SetOperations set = redisTemplate.opsForSet();
        return set.members(key);
    }


    public void zAdd(String key, Object value, double source) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        zset.add(key, value, source);
    }


    public Set rangeByScore(String key, double source, double scoure_1) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        redisTemplate.opsForValue();
        return zset.rangeByScore(key, source, scoure_1);
    }

    public void saveDataToRedis(String name) {
        double index = Math.abs(name.hashCode() % size);
        long indexLong = new Double(index).longValue();
        boolean availableUsers = setBit("availableUsers", indexLong, true);
    }


    public boolean getDataToRedis(String name) {
        double index = Math.abs(name.hashCode() % size);
        long indexLong = new Double(index).longValue();
        return getBit("availableUsers", indexLong);
    }



    public Long zRank(String key, Object value) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        return zset.rank(key, value);
    }


    public Set zRankWithScore(String key, long start, long end) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        Set ret = zset.rangeWithScores(key, start, end);
        return ret;
    }


    public Double zSetScore(String key, Object value) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        return zset.score(key, value);
    }


    public void incrementScore(String key, Object value, double scoure) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        zset.incrementScore(key, value, scoure);
    }


    public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithScore(String key, long start, long end) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        return (Set<ZSetOperations.TypedTuple<Object>>) zset.reverseRangeByScoreWithScores(key, start, end);
    }

    public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithRank(String key, long start, long end) {
        ZSetOperations zset = redisTemplate.opsForZSet();
        return (Set<ZSetOperations.TypedTuple<Object>>) zset.reverseRangeWithScores(key, start, end);
    }
}


================================================
FILE: src/main/java/com/blog/util/SEOUtils.java
================================================
package com.blog.util;

import com.blog.entity.Blog;
import org.springframework.stereotype.Component;

/**
 * SEO 工具类
 * 生成 Meta 标签、结构化数据等
 * @author tangredtea
 */
@Component
public class SEOUtils {
    
    private static final String SITE_NAME = "Spring Blog";
    private stati
Download .txt
gitextract_r_we8a7l/

├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── feature_request.md
│   └── dependabot.yml
├── .gitignore
├── Dockerfile
├── LICENSE.txt
├── README.md
├── README_CN.md
├── blog.sql
├── docker-compose.yml
├── nginx.conf
├── pom.xml
└── src/
    ├── main/
    │   ├── java/
    │   │   └── com/
    │   │       └── blog/
    │   │           ├── BlogApplication.java
    │   │           ├── aspect/
    │   │           │   └── LogAspect.java
    │   │           ├── config/
    │   │           │   ├── RedisConfig.java
    │   │           │   ├── RedisKey.java
    │   │           │   ├── SettingsConfig.java
    │   │           │   └── WebMvcConfig.java
    │   │           ├── controller/
    │   │           │   ├── SitemapController.java
    │   │           │   ├── admin/
    │   │           │   │   ├── AIController.java
    │   │           │   │   ├── AdminController.java
    │   │           │   │   ├── BlogController.java
    │   │           │   │   ├── FriendLinkController.java
    │   │           │   │   ├── SettingsController.java
    │   │           │   │   ├── TagController.java
    │   │           │   │   └── TypeController.java
    │   │           │   ├── blog/
    │   │           │   │   ├── AboutShowController.java
    │   │           │   │   ├── ArchiveShowController.java
    │   │           │   │   ├── FriendLinkControllerShow.java
    │   │           │   │   ├── IndexController.java
    │   │           │   │   ├── MessageController.java
    │   │           │   │   ├── TagShowController.java
    │   │           │   │   └── TypeShowController.java
    │   │           │   └── common/
    │   │           │       └── ControllerExceptionHandler.java
    │   │           ├── dao/
    │   │           │   ├── BlogDao.java
    │   │           │   ├── FriendLinkDao.java
    │   │           │   ├── MessageDao.java
    │   │           │   ├── TagDao.java
    │   │           │   ├── TypeDao.java
    │   │           │   └── UserDao.java
    │   │           ├── entity/
    │   │           │   ├── Blog.java
    │   │           │   ├── BlogAndTag.java
    │   │           │   ├── FriendLink.java
    │   │           │   ├── Message.java
    │   │           │   ├── Tag.java
    │   │           │   ├── Type.java
    │   │           │   └── User.java
    │   │           ├── enums/
    │   │           │   └── BlogStatus.java
    │   │           ├── exception/
    │   │           │   ├── BusinessException.java
    │   │           │   ├── GlobalExceptionHandler.java
    │   │           │   └── NotFoundException.java
    │   │           ├── interceptor/
    │   │           │   └── LoginInterceptor.java
    │   │           ├── pojo/
    │   │           │   ├── RequestLog.java
    │   │           │   └── WebhookMessage.java
    │   │           ├── scheduled/
    │   │           │   └── Refresh.java
    │   │           ├── service/
    │   │           │   ├── AIService.java
    │   │           │   ├── BlogService.java
    │   │           │   ├── FriendLinkService.java
    │   │           │   ├── MessageService.java
    │   │           │   ├── RedisService.java
    │   │           │   ├── SitemapService.java
    │   │           │   ├── SmartSearchService.java
    │   │           │   ├── TagService.java
    │   │           │   ├── TypeService.java
    │   │           │   ├── UserService.java
    │   │           │   └── impl/
    │   │           │       ├── BlogServiceImpl.java
    │   │           │       ├── FriendLinkServiceImpl.java
    │   │           │       ├── MessageServiceImpl.java
    │   │           │       ├── RedisServiceImpl.java
    │   │           │       ├── TagServiceImpl.java
    │   │           │       ├── TypeServiceImpl.java
    │   │           │       └── UserServiceImpl.java
    │   │           └── util/
    │   │               ├── CommonResult.java
    │   │               ├── MD5Utils.java
    │   │               ├── MarkdownUtils.java
    │   │               ├── PasswordUtils.java
    │   │               ├── PropertiesUtil.java
    │   │               ├── RedisUtil.java
    │   │               ├── SEOUtils.java
    │   │               └── WxChatbotClient.java
    │   └── resources/
    │       ├── application-dev.yml
    │       ├── application-pro.yml
    │       ├── application.yml
    │       ├── mapper/
    │       │   ├── BlogDao.xml
    │       │   ├── FriendLinkDao.xml
    │       │   ├── MessageDao.xml
    │       │   ├── TagDao.xml
    │       │   ├── TypeDao.xml
    │       │   └── UserDao.xml
    │       ├── messages.properties
    │       ├── static/
    │       │   ├── backend/
    │       │   │   ├── css/
    │       │   │   │   └── style.css
    │       │   │   ├── icons/
    │       │   │   │   ├── avasta/
    │       │   │   │   │   └── css/
    │       │   │   │   │       └── style.css
    │       │   │   │   ├── bootstrap-icons/
    │       │   │   │   │   └── font/
    │       │   │   │   │       └── bootstrap-icons.css
    │       │   │   │   ├── flaticon/
    │       │   │   │   │   └── flaticon.css
    │       │   │   │   ├── flaticon_1/
    │       │   │   │   │   └── flaticon_1.css
    │       │   │   │   ├── icomoon/
    │       │   │   │   │   └── icomoon.css
    │       │   │   │   ├── simple-line-icons/
    │       │   │   │   │   └── css/
    │       │   │   │   │       └── simple-line-icons.css
    │       │   │   │   └── themify-icons/
    │       │   │   │       └── css/
    │       │   │   │           └── themify-icons.css
    │       │   │   ├── js/
    │       │   │   │   ├── dashboard/
    │       │   │   │   │   ├── coin-details.js
    │       │   │   │   │   ├── dashboard-1.js
    │       │   │   │   │   ├── market-capital.js
    │       │   │   │   │   ├── my-wallet.js
    │       │   │   │   │   └── portofolio.js
    │       │   │   │   ├── demo.js
    │       │   │   │   ├── deznav-init.js
    │       │   │   │   ├── fullcalendar-init.js
    │       │   │   │   ├── plugins-init/
    │       │   │   │   │   ├── bs-daterange-picker-init.js
    │       │   │   │   │   ├── chartist-init.js
    │       │   │   │   │   ├── chartjs-init.js
    │       │   │   │   │   ├── clock-picker-init.js
    │       │   │   │   │   ├── datatables.init.js
    │       │   │   │   │   ├── flot-init.js
    │       │   │   │   │   ├── fullcalendar-init.js
    │       │   │   │   │   ├── jquery-asColorPicker.init.js
    │       │   │   │   │   ├── jquery.validate-init.js
    │       │   │   │   │   ├── jqvmap-init.js
    │       │   │   │   │   ├── material-date-picker-init.js
    │       │   │   │   │   ├── morris-init.js
    │       │   │   │   │   ├── nestable-init.js
    │       │   │   │   │   ├── nouislider-init.js
    │       │   │   │   │   ├── pickadate-init.js
    │       │   │   │   │   ├── piety-init.js
    │       │   │   │   │   ├── select2-init.js
    │       │   │   │   │   ├── sparkline-init.js
    │       │   │   │   │   ├── sweetalert.init.js
    │       │   │   │   │   ├── toastr-init.js
    │       │   │   │   │   └── widgets-script-init.js
    │       │   │   │   └── styleSwitcher.js
    │       │   │   └── vendor/
    │       │   │       ├── jquery-nice-select/
    │       │   │       │   └── css/
    │       │   │       │       └── nice-select.css
    │       │   │       ├── owl-carousel/
    │       │   │       │   ├── owl.carousel.css
    │       │   │       │   └── owl.carousel.js
    │       │   │       └── perfect-scrollbar/
    │       │   │           └── css/
    │       │   │               └── perfect-scrollbar.css
    │       │   ├── css/
    │       │   │   ├── animate.css
    │       │   │   ├── dark-mode.css
    │       │   │   ├── donate.css
    │       │   │   ├── font.css
    │       │   │   ├── foreBlog.css
    │       │   │   ├── friend.css
    │       │   │   ├── themes/
    │       │   │   │   └── default/
    │       │   │   │       └── assets/
    │       │   │   │           └── fonts/
    │       │   │   │               └── icons.otf
    │       │   │   ├── timeline.css
    │       │   │   └── typo.css
    │       │   ├── js/
    │       │   │   ├── article.js
    │       │   │   ├── canvas-ribbon.js
    │       │   │   ├── category.js
    │       │   │   ├── error.js
    │       │   │   ├── foreBlog.js
    │       │   │   ├── home.js
    │       │   │   ├── jquery.js
    │       │   │   ├── tags.js
    │       │   │   ├── theme.js
    │       │   │   └── whatwg-fetch@2.0.3_fetch.js
    │       │   └── lib/
    │       │       ├── editormd/
    │       │       │   ├── css/
    │       │       │   │   ├── editormd.css
    │       │       │   │   ├── editormd.logo.css
    │       │       │   │   └── editormd.preview.css
    │       │       │   ├── editormd.js
    │       │       │   ├── fonts/
    │       │       │   │   └── FontAwesome.otf
    │       │       │   ├── languages/
    │       │       │   │   ├── en.js
    │       │       │   │   └── zh-tw.js
    │       │       │   ├── lib/
    │       │       │   │   └── codemirror/
    │       │       │   │       ├── AUTHORS
    │       │       │   │       ├── LICENSE
    │       │       │   │       ├── README.md
    │       │       │   │       ├── addon/
    │       │       │   │       │   ├── comment/
    │       │       │   │       │   │   ├── comment.js
    │       │       │   │       │   │   └── continuecomment.js
    │       │       │   │       │   ├── dialog/
    │       │       │   │       │   │   ├── dialog.css
    │       │       │   │       │   │   └── dialog.js
    │       │       │   │       │   ├── display/
    │       │       │   │       │   │   ├── fullscreen.css
    │       │       │   │       │   │   ├── fullscreen.js
    │       │       │   │       │   │   ├── panel.js
    │       │       │   │       │   │   ├── placeholder.js
    │       │       │   │       │   │   └── rulers.js
    │       │       │   │       │   ├── edit/
    │       │       │   │       │   │   ├── closebrackets.js
    │       │       │   │       │   │   ├── closetag.js
    │       │       │   │       │   │   ├── continuelist.js
    │       │       │   │       │   │   ├── matchbrackets.js
    │       │       │   │       │   │   ├── matchtags.js
    │       │       │   │       │   │   └── trailingspace.js
    │       │       │   │       │   ├── fold/
    │       │       │   │       │   │   ├── brace-fold.js
    │       │       │   │       │   │   ├── comment-fold.js
    │       │       │   │       │   │   ├── foldcode.js
    │       │       │   │       │   │   ├── foldgutter.css
    │       │       │   │       │   │   ├── foldgutter.js
    │       │       │   │       │   │   ├── indent-fold.js
    │       │       │   │       │   │   ├── markdown-fold.js
    │       │       │   │       │   │   └── xml-fold.js
    │       │       │   │       │   ├── hint/
    │       │       │   │       │   │   ├── anyword-hint.js
    │       │       │   │       │   │   ├── css-hint.js
    │       │       │   │       │   │   ├── html-hint.js
    │       │       │   │       │   │   ├── javascript-hint.js
    │       │       │   │       │   │   ├── show-hint.css
    │       │       │   │       │   │   ├── show-hint.js
    │       │       │   │       │   │   ├── sql-hint.js
    │       │       │   │       │   │   └── xml-hint.js
    │       │       │   │       │   ├── lint/
    │       │       │   │       │   │   ├── coffeescript-lint.js
    │       │       │   │       │   │   ├── css-lint.js
    │       │       │   │       │   │   ├── javascript-lint.js
    │       │       │   │       │   │   ├── json-lint.js
    │       │       │   │       │   │   ├── lint.css
    │       │       │   │       │   │   ├── lint.js
    │       │       │   │       │   │   └── yaml-lint.js
    │       │       │   │       │   ├── merge/
    │       │       │   │       │   │   ├── merge.css
    │       │       │   │       │   │   └── merge.js
    │       │       │   │       │   ├── mode/
    │       │       │   │       │   │   ├── loadmode.js
    │       │       │   │       │   │   ├── multiplex.js
    │       │       │   │       │   │   ├── multiplex_test.js
    │       │       │   │       │   │   ├── overlay.js
    │       │       │   │       │   │   └── simple.js
    │       │       │   │       │   ├── runmode/
    │       │       │   │       │   │   ├── colorize.js
    │       │       │   │       │   │   ├── runmode-standalone.js
    │       │       │   │       │   │   ├── runmode.js
    │       │       │   │       │   │   └── runmode.node.js
    │       │       │   │       │   ├── scroll/
    │       │       │   │       │   │   ├── annotatescrollbar.js
    │       │       │   │       │   │   ├── scrollpastend.js
    │       │       │   │       │   │   ├── simplescrollbars.css
    │       │       │   │       │   │   └── simplescrollbars.js
    │       │       │   │       │   ├── search/
    │       │       │   │       │   │   ├── match-highlighter.js
    │       │       │   │       │   │   ├── matchesonscrollbar.css
    │       │       │   │       │   │   ├── matchesonscrollbar.js
    │       │       │   │       │   │   ├── search.js
    │       │       │   │       │   │   └── searchcursor.js
    │       │       │   │       │   ├── selection/
    │       │       │   │       │   │   ├── active-line.js
    │       │       │   │       │   │   ├── mark-selection.js
    │       │       │   │       │   │   └── selection-pointer.js
    │       │       │   │       │   ├── tern/
    │       │       │   │       │   │   ├── tern.css
    │       │       │   │       │   │   ├── tern.js
    │       │       │   │       │   │   └── worker.js
    │       │       │   │       │   └── wrap/
    │       │       │   │       │       └── hardwrap.js
    │       │       │   │       ├── bower.json
    │       │       │   │       ├── lib/
    │       │       │   │       │   ├── codemirror.css
    │       │       │   │       │   └── codemirror.js
    │       │       │   │       ├── mode/
    │       │       │   │       │   ├── apl/
    │       │       │   │       │   │   ├── apl.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── asterisk/
    │       │       │   │       │   │   ├── asterisk.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── clike/
    │       │       │   │       │   │   ├── clike.js
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── scala.html
    │       │       │   │       │   ├── clojure/
    │       │       │   │       │   │   ├── clojure.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── cobol/
    │       │       │   │       │   │   ├── cobol.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── coffeescript/
    │       │       │   │       │   │   ├── coffeescript.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── commonlisp/
    │       │       │   │       │   │   ├── commonlisp.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── css/
    │       │       │   │       │   │   ├── css.js
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── less.html
    │       │       │   │       │   │   ├── less_test.js
    │       │       │   │       │   │   ├── scss.html
    │       │       │   │       │   │   ├── scss_test.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── cypher/
    │       │       │   │       │   │   ├── cypher.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── d/
    │       │       │   │       │   │   ├── d.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── dart/
    │       │       │   │       │   │   ├── dart.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── diff/
    │       │       │   │       │   │   ├── diff.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── django/
    │       │       │   │       │   │   ├── django.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── dockerfile/
    │       │       │   │       │   │   ├── dockerfile.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── dtd/
    │       │       │   │       │   │   ├── dtd.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── dylan/
    │       │       │   │       │   │   ├── dylan.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── ebnf/
    │       │       │   │       │   │   ├── ebnf.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── ecl/
    │       │       │   │       │   │   ├── ecl.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── eiffel/
    │       │       │   │       │   │   ├── eiffel.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── erlang/
    │       │       │   │       │   │   ├── erlang.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── forth/
    │       │       │   │       │   │   ├── forth.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── fortran/
    │       │       │   │       │   │   ├── fortran.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── gas/
    │       │       │   │       │   │   ├── gas.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── gfm/
    │       │       │   │       │   │   ├── gfm.js
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── gherkin/
    │       │       │   │       │   │   ├── gherkin.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── go/
    │       │       │   │       │   │   ├── go.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── groovy/
    │       │       │   │       │   │   ├── groovy.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── haml/
    │       │       │   │       │   │   ├── haml.js
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── haskell/
    │       │       │   │       │   │   ├── haskell.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── haxe/
    │       │       │   │       │   │   ├── haxe.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── htmlembedded/
    │       │       │   │       │   │   ├── htmlembedded.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── htmlmixed/
    │       │       │   │       │   │   ├── htmlmixed.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── http/
    │       │       │   │       │   │   ├── http.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── idl/
    │       │       │   │       │   │   ├── idl.js
    │       │       │   │       │   │   └── index.html
    │       │       │   │       │   ├── index.html
    │       │       │   │       │   ├── jade/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── jade.js
    │       │       │   │       │   ├── javascript/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── javascript.js
    │       │       │   │       │   │   ├── json-ld.html
    │       │       │   │       │   │   ├── test.js
    │       │       │   │       │   │   └── typescript.html
    │       │       │   │       │   ├── jinja2/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── jinja2.js
    │       │       │   │       │   ├── julia/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── julia.js
    │       │       │   │       │   ├── kotlin/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── kotlin.js
    │       │       │   │       │   ├── livescript/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── livescript.js
    │       │       │   │       │   ├── lua/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── lua.js
    │       │       │   │       │   ├── markdown/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── markdown.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── meta.js
    │       │       │   │       │   ├── mirc/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── mirc.js
    │       │       │   │       │   ├── mllike/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── mllike.js
    │       │       │   │       │   ├── modelica/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── modelica.js
    │       │       │   │       │   ├── nginx/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── nginx.js
    │       │       │   │       │   ├── ntriples/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── ntriples.js
    │       │       │   │       │   ├── octave/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── octave.js
    │       │       │   │       │   ├── pascal/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── pascal.js
    │       │       │   │       │   ├── pegjs/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── pegjs.js
    │       │       │   │       │   ├── perl/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── perl.js
    │       │       │   │       │   ├── php/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── php.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── pig/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── pig.js
    │       │       │   │       │   ├── properties/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── properties.js
    │       │       │   │       │   ├── puppet/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── puppet.js
    │       │       │   │       │   ├── python/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── python.js
    │       │       │   │       │   ├── q/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── q.js
    │       │       │   │       │   ├── r/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── r.js
    │       │       │   │       │   ├── rpm/
    │       │       │   │       │   │   ├── changes/
    │       │       │   │       │   │   │   └── index.html
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── rpm.js
    │       │       │   │       │   ├── rst/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── rst.js
    │       │       │   │       │   ├── ruby/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── ruby.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── rust/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── rust.js
    │       │       │   │       │   ├── sass/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── sass.js
    │       │       │   │       │   ├── scheme/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── scheme.js
    │       │       │   │       │   ├── shell/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── shell.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── sieve/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── sieve.js
    │       │       │   │       │   ├── slim/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── slim.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── smalltalk/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── smalltalk.js
    │       │       │   │       │   ├── smarty/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── smarty.js
    │       │       │   │       │   ├── smartymixed/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── smartymixed.js
    │       │       │   │       │   ├── solr/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── solr.js
    │       │       │   │       │   ├── soy/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── soy.js
    │       │       │   │       │   ├── sparql/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── sparql.js
    │       │       │   │       │   ├── spreadsheet/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── spreadsheet.js
    │       │       │   │       │   ├── sql/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── sql.js
    │       │       │   │       │   ├── stex/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── stex.js
    │       │       │   │       │   │   └── test.js
    │       │       │   │       │   ├── stylus/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── stylus.js
    │       │       │   │       │   ├── tcl/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── tcl.js
    │       │       │   │       │   ├── textile/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── test.js
    │       │       │   │       │   │   └── textile.js
    │       │       │   │       │   ├── tiddlywiki/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── tiddlywiki.css
    │       │       │   │       │   │   └── tiddlywiki.js
    │       │       │   │       │   ├── tiki/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── tiki.css
    │       │       │   │       │   │   └── tiki.js
    │       │       │   │       │   ├── toml/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── toml.js
    │       │       │   │       │   ├── tornado/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── tornado.js
    │       │       │   │       │   ├── turtle/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── turtle.js
    │       │       │   │       │   ├── vb/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── vb.js
    │       │       │   │       │   ├── vbscript/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── vbscript.js
    │       │       │   │       │   ├── velocity/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── velocity.js
    │       │       │   │       │   ├── verilog/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── test.js
    │       │       │   │       │   │   └── verilog.js
    │       │       │   │       │   ├── xml/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── test.js
    │       │       │   │       │   │   └── xml.js
    │       │       │   │       │   ├── xquery/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   ├── test.js
    │       │       │   │       │   │   └── xquery.js
    │       │       │   │       │   ├── yaml/
    │       │       │   │       │   │   ├── index.html
    │       │       │   │       │   │   └── yaml.js
    │       │       │   │       │   └── z80/
    │       │       │   │       │       ├── index.html
    │       │       │   │       │       └── z80.js
    │       │       │   │       └── theme/
    │       │       │   │           ├── 3024-day.css
    │       │       │   │           ├── 3024-night.css
    │       │       │   │           ├── ambiance-mobile.css
    │       │       │   │           ├── ambiance.css
    │       │       │   │           ├── base16-dark.css
    │       │       │   │           ├── base16-light.css
    │       │       │   │           ├── blackboard.css
    │       │       │   │           ├── cobalt.css
    │       │       │   │           ├── colorforth.css
    │       │       │   │           ├── eclipse.css
    │       │       │   │           ├── elegant.css
    │       │       │   │           ├── erlang-dark.css
    │       │       │   │           ├── lesser-dark.css
    │       │       │   │           ├── mbo.css
    │       │       │   │           ├── mdn-like.css
    │       │       │   │           ├── midnight.css
    │       │       │   │           ├── monokai.css
    │       │       │   │           ├── neat.css
    │       │       │   │           ├── neo.css
    │       │       │   │           ├── night.css
    │       │       │   │           ├── paraiso-dark.css
    │       │       │   │           ├── paraiso-light.css
    │       │       │   │           ├── pastel-on-dark.css
    │       │       │   │           ├── rubyblue.css
    │       │       │   │           ├── solarized.css
    │       │       │   │           ├── the-matrix.css
    │       │       │   │           ├── tomorrow-night-bright.css
    │       │       │   │           ├── tomorrow-night-eighties.css
    │       │       │   │           ├── twilight.css
    │       │       │   │           ├── vibrant-ink.css
    │       │       │   │           ├── xq-dark.css
    │       │       │   │           ├── xq-light.css
    │       │       │   │           └── zenburn.css
    │       │       │   └── plugins/
    │       │       │       ├── code-block-dialog/
    │       │       │       │   └── code-block-dialog.js
    │       │       │       ├── emoji-dialog/
    │       │       │       │   ├── emoji-dialog.js
    │       │       │       │   └── emoji.json
    │       │       │       ├── goto-line-dialog/
    │       │       │       │   └── goto-line-dialog.js
    │       │       │       ├── help-dialog/
    │       │       │       │   ├── help-dialog.js
    │       │       │       │   └── help.md
    │       │       │       ├── html-entities-dialog/
    │       │       │       │   ├── html-entities-dialog.js
    │       │       │       │   └── html-entities.json
    │       │       │       ├── image-dialog/
    │       │       │       │   └── image-dialog.js
    │       │       │       ├── link-dialog/
    │       │       │       │   └── link-dialog.js
    │       │       │       ├── plugin-template.js
    │       │       │       ├── preformatted-text-dialog/
    │       │       │       │   └── preformatted-text-dialog.js
    │       │       │       ├── reference-link-dialog/
    │       │       │       │   └── reference-link-dialog.js
    │       │       │       ├── table-dialog/
    │       │       │       │   └── table-dialog.js
    │       │       │       └── test-plugin/
    │       │       │           └── test-plugin.js
    │       │       ├── prism/
    │       │       │   ├── prism.css
    │       │       │   └── prism.js
    │       │       └── tocbot/
    │       │           ├── tocbot.css
    │       │           └── tocbot.js
    │       └── templates/
    │           ├── about.html
    │           ├── admin/
    │           │   ├── ai-assistant.html
    │           │   ├── blogs-input.html
    │           │   ├── blogs.html
    │           │   ├── fragments/
    │           │   │   ├── footer.html
    │           │   │   ├── header.html
    │           │   │   └── sidebar.html
    │           │   ├── friendLinks-input.html
    │           │   ├── friendLinks.html
    │           │   ├── index.html
    │           │   ├── login.html
    │           │   ├── settings.html
    │           │   ├── tags-input.html
    │           │   ├── tags.html
    │           │   ├── types-input.html
    │           │   ├── types.html
    │           │   ├── users-input.html
    │           │   └── users.html
    │           ├── blog.html
    │           ├── error/
    │           │   ├── 404.html
    │           │   ├── 500.html
    │           │   └── error.html
    │           ├── fragments/
    │           │   ├── footer.html
    │           │   └── header.html
    │           ├── friends.html
    │           ├── index.html
    │           ├── message.html
    │           ├── search.html
    │           ├── tags.html
    │           ├── time.html
    │           └── types.html
    └── test/
        └── java/
            └── com/
                └── blog/
                    ├── BlogApplicationTests.java
                    ├── service/
                    │   ├── AIServiceTest.java
                    │   └── SitemapServiceTest.java
                    └── util/
                        ├── PasswordUtilsTest.java
                        └── SEOUtilsTest.java
Download .txt
SYMBOL INDEX (1987 symbols across 230 files)

FILE: blog.sql
  type `t_user` (line 18) | CREATE TABLE IF NOT EXISTS `t_user` (
  type `t_type` (line 37) | CREATE TABLE IF NOT EXISTS `t_type` (
  type `t_tag` (line 57) | CREATE TABLE IF NOT EXISTS `t_tag` (
  type `t_blog` (line 82) | CREATE TABLE IF NOT EXISTS `t_blog` (
  type `t_blog_tags` (line 180) | CREATE TABLE IF NOT EXISTS `t_blog_tags` (
  type `t_message` (line 215) | CREATE TABLE IF NOT EXISTS `t_message` (
  type `t_friend` (line 247) | CREATE TABLE IF NOT EXISTS `t_friend` (
  type `t_settings` (line 269) | CREATE TABLE IF NOT EXISTS `t_settings` (
  type `t_operation_log` (line 297) | CREATE TABLE IF NOT EXISTS `t_operation_log` (

FILE: src/main/java/com/blog/BlogApplication.java
  class BlogApplication (line 12) | @SpringBootApplication
    method main (line 17) | public static void main(String[] args) {

FILE: src/main/java/com/blog/aspect/LogAspect.java
  class LogAspect (line 18) | @Aspect
    method log (line 27) | @Pointcut("execution(* com.blog.controller..*.*(..))")
    method doBefore (line 33) | @Before("log()")
    method doAfter (line 54) | @After("log()")
    method doAfterReturn (line 62) | @AfterReturning(returning = "result", pointcut = "log()")

FILE: src/main/java/com/blog/config/RedisConfig.java
  class RedisConfig (line 20) | @Configuration
    method redisTemplate (line 23) | @Bean

FILE: src/main/java/com/blog/config/RedisKey.java
  class RedisKey (line 6) | public final class RedisKey {

FILE: src/main/java/com/blog/config/SettingsConfig.java
  class SettingsConfig (line 11) | @Component

FILE: src/main/java/com/blog/config/WebMvcConfig.java
  class WebMvcConfig (line 12) | @Configuration
    method addInterceptors (line 20) | @Override

FILE: src/main/java/com/blog/controller/SitemapController.java
  class SitemapController (line 15) | @Controller
    method sitemap (line 25) | @GetMapping(value = "/sitemap.xml", produces = MediaType.APPLICATION_X...

FILE: src/main/java/com/blog/controller/admin/AIController.java
  class AIController (line 17) | @Slf4j
    method assistant (line 28) | @GetMapping("/assistant")
    method generateSummary (line 36) | @PostMapping("/summary")
    method suggestTags (line 51) | @PostMapping("/suggest-tags")
    method scoreArticle (line 67) | @PostMapping("/score")
    method checkStatus (line 87) | @GetMapping("/status")
    method getScoreLevel (line 96) | private String getScoreLevel(int score) {

FILE: src/main/java/com/blog/controller/admin/AdminController.java
  class AdminController (line 21) | @Controller
    method loginPage (line 46) | @GetMapping({"","/","/index","/login"})
    method login (line 66) | @PostMapping("/login")
    method logout (line 83) | @GetMapping("/logout")
    method users (line 89) | @GetMapping("/users")
    method toAddUser (line 98) | @GetMapping("/users/input")
    method toEditUser (line 104) | @GetMapping("/users/{id}/input")
    method addUser (line 110) | @PostMapping("/users")
    method delete (line 124) | @PostMapping("/users/{id}/delete")
    method editUser (line 131) | @PostMapping("/users/{id}")

FILE: src/main/java/com/blog/controller/admin/BlogController.java
  class BlogController (line 23) | @Controller
    method setTypeAndTag (line 39) | public void setTypeAndTag(Model model) {
    method blogs (line 50) | @GetMapping("/blogs")
    method searchBlogs (line 68) | @PostMapping("/blogs/search")
    method toAddBlog (line 85) | @GetMapping("/blogs/input")
    method toEditBlog (line 99) | @GetMapping("/blogs/{id}/input")
    method addBlog (line 118) | @PostMapping("/blogs")
    method deleteBlogs (line 135) | @PostMapping("/blogs/{id}/delete")
    method updateCache (line 143) | public void updateCache(Blog blog){
    method deleteCache (line 149) | public void deleteCache(Long id){

FILE: src/main/java/com/blog/controller/admin/FriendLinkController.java
  class FriendLinkController (line 20) | @Controller
    method friend (line 33) | @GetMapping({"/friendLinks","/friendlinks"})
    method input (line 47) | @GetMapping("/friendLinks/input")
    method post (line 60) | @PostMapping("/friendLinks")
    method editInput (line 87) | @GetMapping("/friendLinks/{id}/input")
    method editPost (line 99) | @PostMapping("/friendLinks/{id}")
    method delete (line 116) | @PostMapping("/friendLinks/{id}/delete")

FILE: src/main/java/com/blog/controller/admin/SettingsController.java
  class SettingsController (line 19) | @Controller
    method messages (line 26) | @GetMapping("/settings")
    method message (line 32) | @PostMapping("/settings")

FILE: src/main/java/com/blog/controller/admin/TagController.java
  class TagController (line 18) | @Controller
    method tags (line 25) | @GetMapping("/tags")
    method toAddTag (line 34) | @GetMapping("/tags/input")
    method toEditTag (line 41) | @GetMapping("/tags/{id}/input")
    method addTag (line 47) | @PostMapping("/tags")
    method editTag (line 60) | @PostMapping("/tags/{id}")
    method delete (line 73) | @PostMapping("/tags/{id}/delete")

FILE: src/main/java/com/blog/controller/admin/TypeController.java
  class TypeController (line 18) | @Controller
    method types (line 25) | @GetMapping("/types")
    method toAddType (line 34) | @GetMapping("/types/input")
    method toEditType (line 41) | @GetMapping("/types/{id}/input")
    method addType (line 47) | @PostMapping("/types")
    method editType (line 60) | @PostMapping("/types/{id}")
    method delete (line 73) | @PostMapping("/types/{id}/delete")

FILE: src/main/java/com/blog/controller/blog/AboutShowController.java
  class AboutShowController (line 9) | @Controller
    method about (line 12) | @GetMapping("/about")

FILE: src/main/java/com/blog/controller/blog/ArchiveShowController.java
  class ArchiveShowController (line 13) | @Controller
    method archives (line 19) | @GetMapping("/time")

FILE: src/main/java/com/blog/controller/blog/FriendLinkControllerShow.java
  class FriendLinkControllerShow (line 13) | @Controller
    method friends (line 19) | @GetMapping("/friends")

FILE: src/main/java/com/blog/controller/blog/IndexController.java
  class IndexController (line 25) | @Controller
    method toIndex (line 52) | @RequestMapping("/")
    method search (line 89) | @GetMapping("/search")
    method getBlog (line 101) | @GetMapping("/blog/{id}")
    method searchSuggestions (line 125) | @GetMapping("/api/search/suggestions")
    method aiChat (line 138) | @PostMapping("/api/ai/chat")
    method aiStatus (line 164) | @GetMapping("/api/ai/status")

FILE: src/main/java/com/blog/controller/blog/MessageController.java
  class MessageController (line 21) | @Controller
    method messages (line 38) | @GetMapping({"/messageComment","/message"})
    method post (line 52) | @PostMapping("/message")
    method delete (line 76) | @PostMapping("/admin/messages/{id}/delete")
    method deleteCache (line 83) | public void deleteCache(){

FILE: src/main/java/com/blog/controller/blog/TagShowController.java
  class TagShowController (line 21) | @Controller
    method types (line 30) | @GetMapping(value = {"/tags/{id}","/tags"})

FILE: src/main/java/com/blog/controller/blog/TypeShowController.java
  class TypeShowController (line 21) | @Controller
    method types (line 30) | @GetMapping(value = {"/types/{id}","/types"})

FILE: src/main/java/com/blog/controller/common/ControllerExceptionHandler.java
  class ControllerExceptionHandler (line 23) | @ControllerAdvice
    method exceptionHandler (line 38) | @ExceptionHandler(Exception.class)

FILE: src/main/java/com/blog/dao/BlogDao.java
  type BlogDao (line 13) | @Mapper
    method getBlog (line 21) | Blog getBlog(Long id);
    method getDetailedBlog (line 28) | Blog getDetailedBlog(@Param("id") Long id);
    method getAllBlog (line 34) | List<Blog> getAllBlog();
    method getAllPublishedBlogs (line 40) | List<Blog> getAllPublishedBlogs();
    method incrementViews (line 47) | int incrementViews(@Param("id") Long id);
    method getByTypeId (line 54) | List<Blog> getByTypeId(Integer typeId);
    method getByTagId (line 61) | List<Blog> getByTagId(Integer tagId);
    method getIndexBlog (line 67) | List<Blog> getIndexBlog();
    method getAllRecommendBlog (line 73) | List<Blog> getAllRecommendBlog();
    method getSearchBlog (line 80) | List<Blog> getSearchBlog(String query);
    method searchAllBlog (line 87) | List<Blog> searchAllBlog(Blog blog);
    method findGroupYear (line 93) | List<String> findGroupYear();
    method findByYear (line 100) | List<Blog> findByYear(@Param("year") String year);
    method saveBlog (line 107) | int saveBlog(Blog blog);
    method saveBlogAndTag (line 114) | int saveBlogAndTag(BlogAndTag blogAndTag);
    method deleteBlogAndTagByBlogId (line 121) | int deleteBlogAndTagByBlogId(@Param("blogId") Long blogId);
    method addViews (line 129) | int addViews(@Param("id") Long id, @Param("increment") int increment);
    method updateBlog (line 136) | int updateBlog(Blog blog);
    method deleteBlog (line 143) | int deleteBlog(Long id);
    method getHotBlog (line 149) | List<Blog> getHotBlog();
    method getCount (line 155) | int getCount();
    method getViews (line 161) | int getViews();
    method getAvgViews (line 167) | int getAvgViews();

FILE: src/main/java/com/blog/dao/FriendLinkDao.java
  type FriendLinkDao (line 11) | @Mapper
    method listFriendLink (line 18) | List<FriendLink> listFriendLink();
    method saveFriendLink (line 25) | int saveFriendLink(FriendLink friendLink);
    method getCount (line 31) | int getCount();
    method getFriendLink (line 38) | FriendLink getFriendLink(Integer id);
    method getFriendLinkByBlogAddress (line 45) | FriendLink getFriendLinkByBlogAddress(String blogAddress);
    method updateFriendLink (line 52) | int updateFriendLink(FriendLink friendLink);
    method deleteFriendLink (line 58) | void deleteFriendLink(Integer id);

FILE: src/main/java/com/blog/dao/MessageDao.java
  type MessageDao (line 12) | @Mapper
    method saveMessage (line 21) | int saveMessage(Message message);
    method getCount (line 27) | int getCount();
    method findByIndexParentId (line 34) | List<Message> findByIndexParentId(@Param("ParentId") Long ParentId);
    method findByParentIdNull (line 41) | List<Message> findByParentIdNull(@Param("ParentId") Long ParentId);
    method findByParentIdNotNull (line 48) | List<Message> findByParentIdNotNull(@Param("id") Long id);
    method findByReplayId (line 55) | List<Message> findByReplayId(@Param("childId") Long childId);
    method deleteMessage (line 61) | void deleteMessage(Long id);

FILE: src/main/java/com/blog/dao/TagDao.java
  type TagDao (line 11) | @Mapper
    method saveTag (line 19) | int saveTag(Tag tag);
    method getCount (line 25) | int getCount();
    method getTag (line 32) | Tag getTag(Integer id);
    method getTagByName (line 39) | Tag getTagByName(String name);
    method getAllTag (line 45) | List<Tag> getAllTag();
    method getBlogTag (line 51) | List<Tag> getBlogTag();
    method updateTag (line 58) | int updateTag(Tag tag);
    method deleteTag (line 65) | int deleteTag(Integer id);

FILE: src/main/java/com/blog/dao/TypeDao.java
  type TypeDao (line 12) | @Mapper
    method saveType (line 20) | int saveType(Type type);
    method getCount (line 26) | int getCount();
    method getType (line 33) | Type getType(Integer id);
    method getTypeByName (line 40) | Type getTypeByName(String name);
    method getAllType (line 46) | List<Type> getAllType();
    method getBlogType (line 52) | List<Type> getBlogType();
    method updateType (line 59) | int updateType(Type type);
    method deleteType (line 66) | int deleteType(Integer id);

FILE: src/main/java/com/blog/dao/UserDao.java
  type UserDao (line 13) | @Mapper
    method queryByUsernameAndPassword (line 22) | User queryByUsernameAndPassword(@Param("username") String username, @P...
    method queryByUsername (line 29) | User queryByUsername(@Param("username") String username);
    method getUserInfoById (line 36) | User getUserInfoById(Integer id);
    method getAllUser (line 42) | List<User> getAllUser();
    method updateUser (line 49) | int updateUser(User user);
    method deleteUser (line 56) | int deleteUser(Integer id);
    method saveUser (line 63) | int saveUser(User user);
    method getCount (line 69) | int getCount();
    method getUserInfoByUsername (line 76) | int getUserInfoByUsername(String name);

FILE: src/main/java/com/blog/entity/Blog.java
  class Blog (line 15) | @Data
    method init (line 57) | public void init(){
    method tagsToIds (line 66) | private String tagsToIds(List<Tag> tags){
    method tagsToNames (line 83) | public String tagsToNames(){

FILE: src/main/java/com/blog/entity/BlogAndTag.java
  class BlogAndTag (line 14) | @Data

FILE: src/main/java/com/blog/entity/FriendLink.java
  class FriendLink (line 13) | @Data

FILE: src/main/java/com/blog/entity/Message.java
  class Message (line 16) | @Data

FILE: src/main/java/com/blog/entity/Tag.java
  class Tag (line 15) | @Data

FILE: src/main/java/com/blog/entity/Type.java
  class Type (line 15) | @Data

FILE: src/main/java/com/blog/entity/User.java
  class User (line 14) | @Data

FILE: src/main/java/com/blog/enums/BlogStatus.java
  type BlogStatus (line 9) | @Getter
    method BlogStatus (line 19) | BlogStatus(int code, String desc) {

FILE: src/main/java/com/blog/exception/BusinessException.java
  class BusinessException (line 9) | @Getter
    method BusinessException (line 14) | public BusinessException(String message) {
    method BusinessException (line 19) | public BusinessException(String message, String redirectUrl) {

FILE: src/main/java/com/blog/exception/GlobalExceptionHandler.java
  class GlobalExceptionHandler (line 13) | @Slf4j
    method handleException (line 20) | @ExceptionHandler(Exception.class)
    method handleBusinessException (line 30) | @ExceptionHandler(BusinessException.class)

FILE: src/main/java/com/blog/exception/NotFoundException.java
  class NotFoundException (line 10) | @ResponseStatus(HttpStatus.NOT_FOUND)
    method NotFoundException (line 13) | public NotFoundException() {
    method NotFoundException (line 16) | public NotFoundException(String message) {
    method NotFoundException (line 20) | public NotFoundException(String message, Throwable cause) {

FILE: src/main/java/com/blog/interceptor/LoginInterceptor.java
  class LoginInterceptor (line 12) | public class LoginInterceptor implements HandlerInterceptor {
    method preHandle (line 14) | @Override

FILE: src/main/java/com/blog/pojo/RequestLog.java
  class RequestLog (line 9) | @Data

FILE: src/main/java/com/blog/pojo/WebhookMessage.java
  class WebhookMessage (line 12) | @Data
    method toJsonString (line 21) | public Map<String, Object> toJsonString() {

FILE: src/main/java/com/blog/scheduled/Refresh.java
  class Refresh (line 20) | @Slf4j
    method init (line 39) | @PostConstruct
    method execute (line 44) | @Scheduled(cron = "0 0 0/4 * * ? ")
    method refreshCaches (line 67) | private void refreshCaches() {

FILE: src/main/java/com/blog/service/AIService.java
  class AIService (line 20) | @Slf4j
    method init (line 36) | @PostConstruct
    method generateSummary (line 52) | public String generateSummary(String content) {
    method suggestTags (line 74) | public String[] suggestTags(String title, String content) {
    method generateReply (line 120) | public String generateReply(String comment, String articleTitle) {
    method scoreArticle (line 145) | public ArticleScore scoreArticle(String title, String content) {
    method callAI (line 193) | private String callAI(String prompt) {
    method generateLocalSummary (line 234) | private String generateLocalSummary(String content) {
    method chatAboutArticle (line 251) | public String chatAboutArticle(String articleTitle, String articleCont...
    method isEnabled (line 282) | public boolean isEnabled() {
    class ArticleScore (line 289) | public static class ArticleScore {
      method ArticleScore (line 293) | public ArticleScore(int score, String suggestion) {
      method getScore (line 298) | public int getScore() { return score; }
      method getSuggestion (line 299) | public String getSuggestion() { return suggestion; }

FILE: src/main/java/com/blog/service/BlogService.java
  type BlogService (line 11) | public interface BlogService {
    method getBlog (line 18) | Blog getBlog(Long id);
    method getDetailedBlog (line 25) | Blog getDetailedBlog(Long id);
    method getAllBlog (line 31) | List<Blog> getAllBlog();
    method getByTypeId (line 38) | List<Blog> getByTypeId(Integer typeId);
    method getByTagId (line 45) | List<Blog> getByTagId(Integer tagId);
    method getIndexBlog (line 51) | List<Blog> getIndexBlog();
    method getAllRecommendBlog (line 57) | List<Blog> getAllRecommendBlog();
    method getSearchBlog (line 64) | List<Blog> getSearchBlog(String query);
    method archiveBlog (line 70) | Map<String,List<Blog>> archiveBlog();
    method countBlog (line 76) | int countBlog();
    method saveBlog (line 83) | int saveBlog(Blog blog);
    method updateBlog (line 90) | int updateBlog(Blog blog);
    method deleteBlog (line 97) | int deleteBlog(Long id);
    method searchAllBlog (line 104) | List<Blog> searchAllBlog(Blog blog);
    method getHotBlog (line 110) | List<Blog> getHotBlog();
    method getTotalViews (line 116) | int getTotalViews();
    method getAvgViews (line 122) | int getAvgViews();

FILE: src/main/java/com/blog/service/FriendLinkService.java
  type FriendLinkService (line 10) | public interface FriendLinkService {
    method listFriendLink (line 16) | List<FriendLink> listFriendLink();
    method saveFriendLink (line 23) | int saveFriendLink(FriendLink friendLink);
    method getFriendLink (line 30) | FriendLink getFriendLink(Integer id);
    method getFriendLinkByBlogAddress (line 37) | FriendLink getFriendLinkByBlogAddress(String blogAddress);
    method updateFriendLink (line 44) | int updateFriendLink(FriendLink friendLink);
    method deleteFriendLink (line 50) | void deleteFriendLink(Integer id);
    method countFriendLink (line 56) | int countFriendLink();

FILE: src/main/java/com/blog/service/MessageService.java
  type MessageService (line 10) | public interface MessageService {
    method listMessage (line 16) | List<Message> listMessage();
    method findByIndexParentId (line 22) | List<Message> findByIndexParentId();
    method saveMessage (line 29) | int saveMessage(Message message);
    method deleteMessage (line 35) | void deleteMessage(Long id);
    method countMessage (line 41) | int countMessage();

FILE: src/main/java/com/blog/service/RedisService.java
  type RedisService (line 20) | @SuppressWarnings("all")
    method set (line 29) | void set(String key, Object value, long time);
    method set (line 37) | void set(String key, Object value);
    method get (line 45) | Object get(String key);
    method del (line 53) | Boolean del(String key);
    method del (line 61) | Long del(List<String> keys);
    method expire (line 70) | Boolean expire(String key, long time);
    method getExpire (line 78) | Long getExpire(String key);
    method hasKey (line 86) | Boolean hasKey(String key);
    method incr (line 95) | Long incr(String key, long delta);
    method decr (line 104) | Long decr(String key, long delta);
    method hGet (line 113) | Object hGet(String key, String hashKey);
    method hSet (line 124) | Boolean hSet(String key, String hashKey, Object value, long time);
    method hSet (line 133) | void hSet(String key, String hashKey, Object value);
    method hGetAll (line 141) | Map hGetAll(String key);
    method hSetAll (line 151) | Boolean hSetAll(String key, Map<String, Object> map, long time);
    method hSetAll (line 159) | void hSetAll(String key, Map<String, ?> map);
    method hDel (line 167) | void hDel(String key, Object... hashKey);
    method hHasKey (line 176) | Boolean hHasKey(String key, String hashKey);
    method hIncr (line 186) | Long hIncr(String key, String hashKey, Long delta);
    method hDecr (line 196) | Long hDecr(String key, String hashKey, Long delta);
    method sMembers (line 204) | Set<Object> sMembers(String key);
    method sAdd (line 213) | Long sAdd(String key, Object... values);
    method sAdd (line 223) | Long sAdd(String key, long time, Object... values);
    method sIsMember (line 232) | Boolean sIsMember(String key, Object value);
    method sSize (line 240) | Long sSize(String key);
    method sRemove (line 249) | Long sRemove(String key, Object... values);
    method lRange (line 259) | List<Object> lRange(String key, long start, long end);
    method lSize (line 267) | Long lSize(String key);
    method lIndex (line 276) | Object lIndex(String key, long index);
    method lPush (line 285) | Long lPush(String key, Object value);
    method lPush (line 295) | Long lPush(String key, Object value, long time);
    method lPushAll (line 304) | Long lPushAll(String key, Object... values);
    method lPushAll (line 314) | Long lPushAll(String key, Long time, Object... values);
    method lRemove (line 324) | Long lRemove(String key, long count, Object value);
    method bitAdd (line 334) | Boolean bitAdd(String key, int offset, boolean b);
    method bitGet (line 343) | Boolean bitGet(String key, int offset);
    method bitCount (line 351) | Long bitCount(String key);
    method bitField (line 361) | List<Long> bitField(String key, int limit, int offset);
    method bitGetAll (line 369) | byte[] bitGetAll(String key);
    method geoAdd (line 380) | Long geoAdd(String key, Double x, Double y, String name);
    method geoGetPointList (line 389) | List<Point> geoGetPointList(String key, Object... place);
    method geoCalculationDistance (line 399) | Distance geoCalculationDistance(String key, String placeOne, String pl...
    method geoNearByPlace (line 411) | GeoResults<RedisGeoCommands.GeoLocation<Object>> geoNearByPlace(String...
    method geoGetHash (line 420) | List<String> geoGetHash(String key, String... place);

FILE: src/main/java/com/blog/service/SitemapService.java
  class SitemapService (line 17) | @Slf4j
    method generateSitemap (line 30) | public String generateSitemap() {
    method addUrl (line 63) | private void addUrl(StringBuilder sb, String loc, String priority, Str...
    method addUrl (line 67) | private void addUrl(StringBuilder sb, String loc, String priority, Str...

FILE: src/main/java/com/blog/service/SmartSearchService.java
  class SmartSearchService (line 17) | @Slf4j
    method getCachedPublishedBlogs (line 29) | private List<Blog> getCachedPublishedBlogs() {
    method extractKeywords (line 43) | public List<String> extractKeywords(String text) {
    method calculateSimilarity (line 79) | public double calculateSimilarity(Blog blog1, Blog blog2) {
    method getRelatedBlogs (line 111) | public List<Blog> getRelatedBlogs(Long blogId, int limit) {
    method getSearchSuggestions (line 134) | public List<String> getSearchSuggestions(String query) {

FILE: src/main/java/com/blog/service/TagService.java
  type TagService (line 10) | public interface TagService {
    method saveTag (line 16) | int saveTag(Tag tag);
    method getTag (line 23) | Tag getTag(Integer id);
    method getTagByName (line 30) | Tag getTagByName(String name);
    method getAllTag (line 36) | List<Tag> getAllTag();
    method getBlogTag (line 42) | List<Tag> getBlogTag();
    method getTagByString (line 49) | List<Tag> getTagByString(String text);
    method updateTag (line 56) | int updateTag(Tag tag);
    method deleteTag (line 63) | int deleteTag(Integer id);
    method countTag (line 69) | int countTag();

FILE: src/main/java/com/blog/service/TypeService.java
  type TypeService (line 10) | public interface TypeService {
    method saveType (line 16) | int saveType(Type type);
    method getType (line 23) | Type getType(Integer id);
    method getTypeByName (line 30) | Type getTypeByName(String name);
    method getAllType (line 36) | List<Type> getAllType();
    method getBlogType (line 42) | List<Type> getBlogType();
    method updateType (line 49) | int updateType(Type type);
    method deleteType (line 56) | int deleteType(Integer id);
    method countType (line 62) | int countType();

FILE: src/main/java/com/blog/service/UserService.java
  type UserService (line 11) | public interface UserService {
    method checkUser (line 19) | User checkUser(@Param("username") String username, @Param("password") ...
    method getUserInfoById (line 26) | User getUserInfoById(Integer id);
    method updateUser (line 33) | int updateUser(User user);
    method saveUser (line 40) | int saveUser(User user);
    method getUsers (line 46) | List<User> getUsers();
    method deleteUser (line 53) | int deleteUser(Integer id);
    method getUserInfoByUsername (line 60) | int getUserInfoByUsername(String name);
    method countUser (line 66) | int countUser();

FILE: src/main/java/com/blog/service/impl/BlogServiceImpl.java
  class BlogServiceImpl (line 22) | @Service
    method getBlog (line 31) | @Override
    method getDetailedBlog (line 36) | @Override
    method getAllBlog (line 46) | @Override
    method getByTypeId (line 51) | @Override
    method getByTagId (line 56) | @Override
    method getIndexBlog (line 61) | @Override
    method getAllRecommendBlog (line 66) | @Override
    method getSearchBlog (line 71) | @Override
    method archiveBlog (line 76) | @Override
    method countBlog (line 86) | @Override
    method getTotalViews (line 91) | @Override
    method getAvgViews (line 96) | @Override
    method searchAllBlog (line 101) | @Override
    method getHotBlog (line 106) | @Override
    method saveBlog (line 116) | @Transactional(rollbackFor = Exception.class)
    method updateBlog (line 132) | @Transactional(rollbackFor = Exception.class)
    method deleteBlog (line 148) | @Override

FILE: src/main/java/com/blog/service/impl/FriendLinkServiceImpl.java
  class FriendLinkServiceImpl (line 14) | @Service
    method listFriendLink (line 20) | @Override
    method saveFriendLink (line 25) | @Override
    method getFriendLink (line 30) | @Override
    method getFriendLinkByBlogAddress (line 35) | @Override
    method updateFriendLink (line 40) | @Override
    method deleteFriendLink (line 45) | @Override
    method countFriendLink (line 50) | @Override

FILE: src/main/java/com/blog/service/impl/MessageServiceImpl.java
  class MessageServiceImpl (line 16) | @Service
    method findByIndexParentId (line 26) | @Override
    method listMessage (line 35) | @Override
    method combineChildren (line 58) | private void combineChildren(List<Message> tempReplies, List<Message> ...
    method recursively (line 76) | private void recursively(List<Message> tempReplies, Long childId, Stri...
    method saveMessage (line 94) | @Override
    method deleteMessage (line 104) | @Override
    method countMessage (line 109) | @Override

FILE: src/main/java/com/blog/service/impl/RedisServiceImpl.java
  class RedisServiceImpl (line 25) | @Service
    method set (line 31) | @Override
    method set (line 36) | @Override
    method get (line 41) | @Override
    method del (line 46) | @Override
    method del (line 51) | @Override
    method expire (line 56) | @Override
    method getExpire (line 61) | @Override
    method hasKey (line 66) | @Override
    method incr (line 71) | @Override
    method decr (line 76) | @Override
    method hGet (line 81) | @Override
    method hSet (line 86) | @Override
    method hSet (line 92) | @Override
    method hGetAll (line 97) | @Override
    method hSetAll (line 102) | @Override
    method hSetAll (line 108) | @Override
    method hDel (line 113) | @Override
    method hHasKey (line 118) | @Override
    method hIncr (line 123) | @Override
    method hDecr (line 128) | @Override
    method sMembers (line 133) | @Override
    method sAdd (line 138) | @Override
    method sAdd (line 143) | @Override
    method sIsMember (line 150) | @Override
    method sSize (line 155) | @Override
    method sRemove (line 160) | @Override
    method lRange (line 165) | @Override
    method lSize (line 170) | @Override
    method lIndex (line 175) | @Override
    method lPush (line 180) | @Override
    method lPush (line 185) | @Override
    method lPushAll (line 192) | @Override
    method lPushAll (line 197) | @Override
    method lRemove (line 204) | @Override
    method bitAdd (line 209) | @Override
    method bitGet (line 214) | @Override
    method bitCount (line 219) | @Override
    method bitField (line 224) | @Override
    method bitGetAll (line 231) | @Override
    method geoAdd (line 236) | @Override
    method geoGetPointList (line 241) | @Override
    method geoCalculationDistance (line 246) | @Override
    method geoNearByPlace (line 252) | @Override
    method geoGetHash (line 266) | @Override

FILE: src/main/java/com/blog/service/impl/TagServiceImpl.java
  class TagServiceImpl (line 14) | @Service
    method saveTag (line 20) | @Override
    method getTag (line 25) | @Override
    method getTagByName (line 30) | @Override
    method getAllTag (line 35) | @Override
    method getBlogTag (line 40) | @Override
    method getTagByString (line 50) | @Override
    method convertToList (line 65) | private List<Integer> convertToList(String ids) {
    method updateTag (line 76) | @Override
    method deleteTag (line 81) | @Override
    method countTag (line 86) | @Override

FILE: src/main/java/com/blog/service/impl/TypeServiceImpl.java
  class TypeServiceImpl (line 15) | @Service
    method saveType (line 24) | @Override
    method getType (line 29) | @Override
    method getTypeByName (line 34) | @Override
    method getAllType (line 39) | @Override
    method getBlogType (line 45) | @Override
    method updateType (line 50) | @Override
    method deleteType (line 55) | @Override
    method countType (line 60) | @Override

FILE: src/main/java/com/blog/service/impl/UserServiceImpl.java
  class UserServiceImpl (line 17) | @Slf4j
    method checkUser (line 25) | @Override
    method getUserInfoById (line 34) | @Override
    method updateUser (line 39) | @Override
    method saveUser (line 44) | @Override
    method getUsers (line 49) | @Override
    method deleteUser (line 54) | @Override
    method getUserInfoByUsername (line 59) | @Override
    method countUser (line 64) | @Override

FILE: src/main/java/com/blog/util/CommonResult.java
  class CommonResult (line 9) | @Data
    method CommonResult (line 15) | public CommonResult(int code, String msg, T data) {
    method success (line 21) | public static <T> CommonResult<T> success(T t) {
    method error (line 25) | public static <T> CommonResult<T> error(T t) {

FILE: src/main/java/com/blog/util/MD5Utils.java
  class MD5Utils (line 9) | public class MD5Utils {
    method code (line 16) | public static String code(String str){

FILE: src/main/java/com/blog/util/MarkdownUtils.java
  class MarkdownUtils (line 19) | public class MarkdownUtils {
    method markdownToHtml (line 26) | public static String markdownToHtml(String markdown) {
    method markdownToHtmlExtensions (line 39) | public static String markdownToHtmlExtensions(String markdown) {
    class CustomAttributeProvider (line 59) | static class CustomAttributeProvider implements AttributeProvider {
      method setAttributes (line 60) | @Override

FILE: src/main/java/com/blog/util/PasswordUtils.java
  class PasswordUtils (line 10) | public class PasswordUtils {
    method encode (line 19) | public static String encode(String rawPassword) {
    method matches (line 29) | public static boolean matches(String rawPassword, String encodedPasswo...

FILE: src/main/java/com/blog/util/PropertiesUtil.java
  class PropertiesUtil (line 15) | @Slf4j
    method getValueByKey (line 28) | public static String getValueByKey(String filePath, String key) {
    method writeProperties (line 39) | public static void writeProperties(String pKey, String pValue) throws ...
    method write (line 50) | public static void write(Class<?> t, SettingsConfig settings) throws I...

FILE: src/main/java/com/blog/util/RedisUtil.java
  class RedisUtil (line 15) | @Component
    method setBit (line 24) | public boolean setBit(String key, long offset, boolean isShow) {
    method getBit (line 37) | public boolean getBit(String key, long offset) {
    method set (line 55) | public boolean set(final String key, Object value) {
    method set (line 74) | public boolean set(final String key, Object value, Long expireTime) {
    method remove (line 92) | public void remove(final String... keys) {
    method remove (line 103) | public void remove(final String key) {
    method exists (line 109) | public boolean exists(final String key) {
    method get (line 114) | public Object get(final String key) {
    method hmSet (line 121) | public void hmSet(String key, Object hashKey, Object value) {
    method hmGet (line 126) | public Object hmGet(String key, Object hashKey) {
    method lPush (line 132) | public void lPush(String k, Object v) {
    method lRange (line 138) | public List lRange(String k, long l, long l1) {
    method add (line 144) | public void add(String key, Object value) {
    method setMembers (line 150) | public Set setMembers(String key) {
    method zAdd (line 156) | public void zAdd(String key, Object value, double source) {
    method rangeByScore (line 162) | public Set rangeByScore(String key, double source, double scoure_1) {
    method saveDataToRedis (line 168) | public void saveDataToRedis(String name) {
    method getDataToRedis (line 175) | public boolean getDataToRedis(String name) {
    method zRank (line 183) | public Long zRank(String key, Object value) {
    method zRankWithScore (line 189) | public Set zRankWithScore(String key, long start, long end) {
    method zSetScore (line 196) | public Double zSetScore(String key, Object value) {
    method incrementScore (line 202) | public void incrementScore(String key, Object value, double scoure) {
    method reverseZRankWithScore (line 208) | public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithScore(St...
    method reverseZRankWithRank (line 213) | public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithRank(Str...

FILE: src/main/java/com/blog/util/SEOUtils.java
  class SEOUtils (line 11) | @Component
    method generateMetaDescription (line 20) | public String generateMetaDescription(Blog blog) {
    method generateMetaKeywords (line 33) | public String generateMetaKeywords(Blog blog) {
    method generateOpenGraphTags (line 57) | public String generateOpenGraphTags(Blog blog, String currentUrl) {
    method generateTwitterCardTags (line 82) | public String generateTwitterCardTags(Blog blog) {
    method generateJsonLd (line 98) | public String generateJsonLd(Blog blog, String currentUrl) {
    method generateBreadcrumbJsonLd (line 142) | public String generateBreadcrumbJsonLd(String... items) {
    method generateFaqJsonLd (line 172) | public String generateFaqJsonLd(String[][] faqs) {
    method truncate (line 200) | private String truncate(String str, int maxLength) {
    method escapeHtml (line 207) | private String escapeHtml(String str) {
    method escapeJson (line 215) | private String escapeJson(String str) {

FILE: src/main/java/com/blog/util/WxChatbotClient.java
  class WxChatbotClient (line 34) | public class WxChatbotClient {
    method send (line 63) | public static boolean send(String webhook, WebhookMessage message) thr...
    method createSSLConnSocketFactory (line 84) | private static SSLConnectionSocketFactory createSSLConnSocketFactory() {

FILE: src/main/resources/static/backend/js/demo.js
  function setCookie (line 20) | function setCookie(cname, cvalue, exhours)
  function getCookie (line 28) | function getCookie(cname)
  function deleteCookie (line 45) | function deleteCookie(cname)
  function deleteAllCookie (line 54) | function deleteAllCookie(reload = true)
  function themeChange (line 162) | function themeChange(theme){
  function setThemeInCookie (line 171) | function setThemeInCookie(themeSettings)
  function setThemeLogo (line 179) | function setThemeLogo() {
  function setThemeOptionOnPage (line 193) | function setThemeOptionOnPage()

FILE: src/main/resources/static/backend/js/deznav-init.js
  function getUrlParams (line 6) | function getUrlParams(dParam)

FILE: src/main/resources/static/backend/js/plugins-init/flot-init.js
  function showTooltip (line 410) | function showTooltip(x, y, contents) {
  function getRandomData (line 424) | function getRandomData() {
  function update_plot4 (line 480) | function update_plot4() {
  function getRandomData (line 490) | function getRandomData() {
  function update_plot5 (line 550) | function update_plot5() {

FILE: src/main/resources/static/backend/js/plugins-init/nouislider-init.js
  function timestamp (line 49) | function timestamp(str) {
  function nth (line 101) | function nth(d) {
  function formatDate (line 116) | function formatDate(date) {
  function crossUpdate (line 216) | function crossUpdate(value, slider) {
  function setLockedValues (line 265) | function setLockedValues() {
  function clickOnPip (line 297) | function clickOnPip() {
  function toggle (line 1061) | function toggle(element) {
  function updateSliderRange (line 1132) | function updateSliderRange(min, max) {

FILE: src/main/resources/static/backend/js/plugins-init/select2-init.js
  function formatRepo (line 86) | function formatRepo (repo) {
  function formatRepoSelection (line 110) | function formatRepoSelection (repo) {
  function matchCustom (line 204) | function matchCustom(params, data) {
  function matchStart (line 236) | function matchStart(params, data) {

FILE: src/main/resources/static/backend/js/plugins-init/sparkline-init.js
  function getSparkLineGraphBlockSize (line 8) | function getSparkLineGraphBlockSize(selector)

FILE: src/main/resources/static/backend/js/plugins-init/widgets-script-init.js
  function getRandomData (line 1149) | function getRandomData() {
  function update_chart (line 1208) | function update_chart() {

FILE: src/main/resources/static/backend/js/styleSwitcher.js
  function addSwitcher (line 3) | function addSwitcher()

FILE: src/main/resources/static/backend/vendor/owl-carousel/owl.carousel.js
  function e (line 1) | function e(b,c){this.settings=null,this.options=a.extend({},e.Defaults,c...
  function e (line 1) | function e(b,c){var e=!1,f=b.charAt(0).toUpperCase()+b.slice(1);return a...
  function f (line 1) | function f(a){return e(a,!0)}

FILE: src/main/resources/static/js/category.js
  function jumpPage (line 4) | function jumpPage(pageNumber) {

FILE: src/main/resources/static/js/foreBlog.js
  function formatDate (line 10) | function formatDate(value,formatString){
  function parseUrl (line 19) | function parseUrl(){
  function getParamValue (line 35) | function getParamValue(name,dataArray){
  function isEmpty (line 44) | function isEmpty(object,name){
  function isEmail (line 56) | function isEmail(value) {
  function isNumber (line 71) | function isNumber(value,text) {
  function isInt (line 88) | function isInt(value,text) {
  function errorShowAndJump (line 106) | function errorShowAndJump(message,address){
  function errorShow (line 115) | function errorShow(message){
  function tipShow (line 124) | function tipShow(message){
  function showModal (line 133) | function showModal(modalName) {
  function closeModal (line 137) | function closeModal(modalName) {
  function isMP4 (line 141) | function isMP4(value,name) {
  function getTotalCommentNumber (line 157) | function getTotalCommentNumber(vue){
  function elementEnableLazyLoadOfRequest (line 169) | function elementEnableLazyLoadOfRequest(className, callback) {
  function multiScaleAnimation (line 183) | function multiScaleAnimation(fatherClassName, subClassName) {
  function simpleScaleAnimation (line 200) | function simpleScaleAnimation(className) {
  function simpleAnimationOfId (line 218) | function simpleAnimationOfId(fatherClass, id, animation, duration) {
  function simpleAnimation (line 233) | function simpleAnimation(className, animation) {
  function secondToDate (line 248) | function secondToDate(second) {
  function setTime (line 274) | function setTime() {
  function isSatisfactoryForKeyword (line 285) | function isSatisfactoryForKeyword(keyword){
  function setPagination (line 294) | function setPagination(totalPage,currentPage){

FILE: src/main/resources/static/js/home.js
  function jumpPage (line 3) | function jumpPage(pageNumber) {

FILE: src/main/resources/static/js/jquery.js
  function j (line 280) | function j(e) {
  function it (line 368) | function it(e) {
  function ot (line 372) | function ot() {
  function st (line 379) | function st(e) {
  function at (line 383) | function at(e) {
  function ut (line 394) | function ut(e, t, n, r) {
  function lt (line 551) | function lt(e, t) {
  function ct (line 561) | function ct(e, t, n) {
  function ft (line 566) | function ft(e, t, n) {
  function pt (line 571) | function pt(e) {
  function ht (line 578) | function ht(e) {
  function dt (line 585) | function dt(e) {
  function gt (line 842) | function gt(e, t) {
  function mt (line 862) | function mt(e) {
  function yt (line 870) | function yt(e, t, r) {
  function vt (line 891) | function vt(e) {
  function xt (line 900) | function xt(e, t, n, r, i) {
  function bt (line 909) | function bt(e, t, n, r, i, o) {
  function wt (line 935) | function wt(e) {
  function Tt (line 962) | function Tt(e, t) {
  function Ct (line 1012) | function Ct(e, t, n) {
  function kt (line 1019) | function kt(e, t, n, i) {
  function Nt (line 1040) | function Nt() {}
  function A (line 1059) | function A(e) {
  function F (line 1221) | function F() {
  function P (line 1330) | function P(e, t, n) {
  function U (line 1602) | function U() {
  function Y (line 1606) | function Y() {
  function V (line 1610) | function V() {
  function K (line 1942) | function K(e, t) {
  function Z (line 2014) | function Z(e, t, n) {
  function ct (line 2219) | function ct(e, t) {
  function ft (line 2224) | function ft(e) {
  function pt (line 2228) | function pt(e) {
  function ht (line 2233) | function ht(e, t) {
  function dt (line 2239) | function dt(e, t) {
  function gt (line 2251) | function gt(e, t) {
  function mt (line 2257) | function mt(e, t) {
  function Dt (line 2315) | function Dt(e, t) {
  function At (line 2325) | function At(e, t) {
  function Lt (line 2329) | function Lt(t) {
  function qt (line 2333) | function qt(e, t) {
  function Ht (line 2420) | function Ht(e, t, n) {
  function Ot (line 2425) | function Ot(e, t, n, r, i) {
  function Ft (line 2434) | function Ft(e, t, n) {
  function Pt (line 2446) | function Pt(e) {
  function Rt (line 2454) | function Rt(e, t) {
  function zt (line 2548) | function zt(e, t, n, r) {
  function an (line 2601) | function an(e) {
  function un (line 2612) | function un(e, t, n, r) {
  function ln (line 2626) | function ln(e, t) {
  function k (line 2777) | function k(e, t, o, a) {
  function cn (line 2808) | function cn(e, t, n) {
  function fn (line 2831) | function fn(e, t, n, r) {
  function Nn (line 2980) | function Nn() {
  function En (line 2986) | function En(e, t) {
  function Sn (line 2996) | function Sn(e, t, n) {
  function jn (line 3046) | function jn(e, t) {
  function Dn (line 3067) | function Dn(e, t, n) {
  function An (line 3106) | function An(e, t, n, r, i) {
  function Ln (line 3197) | function Ln(e, t) {
  function qn (line 3322) | function qn(e) {

FILE: src/main/resources/static/js/tags.js
  function update (line 76) | function update() {
  function depthSort (line 128) | function depthSort() {
  function positionAll (line 153) | function positionAll() {
  function doPosition (line 197) | function doPosition() {
  function sineCosine (line 211) | function sineCosine(a, b, c) {

FILE: src/main/resources/static/js/theme.js
  function initTheme (line 12) | function initTheme() {
  function toggleTheme (line 22) | function toggleTheme() {
  function getCurrentTheme (line 33) | function getCurrentTheme() {

FILE: src/main/resources/static/js/whatwg-fetch@2.0.3_fetch.js
  function normalizeName (line 45) | function normalizeName(name) {
  function normalizeValue (line 55) | function normalizeValue(value) {
  function iteratorFor (line 63) | function iteratorFor(items) {
  function Headers (line 80) | function Headers(headers) {
  function consumed (line 152) | function consumed(body) {
  function fileReaderReady (line 159) | function fileReaderReady(reader) {
  function readBlobAsArrayBuffer (line 170) | function readBlobAsArrayBuffer(blob) {
  function readBlobAsText (line 177) | function readBlobAsText(blob) {
  function readArrayBufferAsText (line 184) | function readArrayBufferAsText(buf) {
  function bufferClone (line 194) | function bufferClone(buf) {
  function Body (line 204) | function Body() {
  function normalizeMethod (line 300) | function normalizeMethod(method) {
  function Request (line 305) | function Request(input, options) {
  function decode (line 346) | function decode(body) {
  function parseHeaders (line 359) | function parseHeaders(rawHeaders) {
  function Response (line 374) | function Response(bodyInit, options) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/comment/comment.js
  function firstNonWS (line 18) | function firstNonWS(str) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/comment/continuecomment.js
  function continueComment (line 17) | function continueComment(cm) {
  function continueLineCommentEnabled (line 64) | function continueLineCommentEnabled(cm) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/dialog/dialog.js
  function dialogDiv (line 14) | function dialogDiv(cm, template, bottom) {
  function closeNotification (line 31) | function closeNotification(cm, newVal) {
  function close (line 44) | function close(newVal) {
  function close (line 100) | function close() {
  function close (line 138) | function close() {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/display/fullscreen.js
  function setFullscreen (line 21) | function setFullscreen(cm) {
  function setNormal (line 32) | function setNormal(cm) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/display/panel.js
  function Panel (line 26) | function Panel(cm, node, options, height) {
  function initPanels (line 50) | function initPanels(cm) {
  function removePanels (line 84) | function removePanels(cm) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/display/placeholder.js
  function clearPlaceholder (line 29) | function clearPlaceholder(cm) {
  function setPlaceholder (line 35) | function setPlaceholder(cm) {
  function onBlur (line 44) | function onBlur(cm) {
  function onChange (line 47) | function onChange(cm) {
  function isEmpty (line 55) | function isEmpty(cm) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/display/rulers.js
  function clearRulers (line 25) | function clearRulers(cm) {
  function setRulers (line 33) | function setRulers(cm) {
  function refreshRulers (line 60) | function refreshRulers(cm) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/edit/closebrackets.js
  function charsAround (line 35) | function charsAround(cm, pos) {
  function enteringString (line 44) | function enteringString(cm, pos, ch) {
  function buildKeymap (line 57) | function buildKeymap(pairs, triples) {
  function buildExplodeHandler (line 140) | function buildExplodeHandler(pairs) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/edit/closetag.js
  function autoCloseGT (line 53) | function autoCloseGT(cm) {
  function autoCloseCurrent (line 97) | function autoCloseCurrent(cm, typingSlash) {
  function autoCloseSlash (line 132) | function autoCloseSlash(cm) {
  function indexOf (line 139) | function indexOf(collection, elt) {
  function closingTagExists (line 148) | function closingTagExists(cm, tagName, pos, state, newTag) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/edit/matchbrackets.js
  function findMatchingBracket (line 19) | function findMatchingBracket(cm, where, strict, config) {
  function scanForBracket (line 40) | function scanForBracket(cm, where, dir, style, config) {
  function matchBrackets (line 67) | function matchBrackets(cm, autoclear, config) {
  function doMatchBrackets (line 97) | function doMatchBrackets(cm) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/edit/matchtags.js
  function clear (line 28) | function clear(cm) {
  function doMatchTags (line 34) | function doMatchTags(cm) {
  function maybeUpdateMatch (line 55) | function maybeUpdateMatch(cm) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/brace-fold.js
  function findOpening (line 18) | function findOpening(openCh) {
  function hasImport (line 63) | function hasImport(line) {
  function hasInclude (line 87) | function hasInclude(line) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/foldcode.js
  function doFold (line 14) | function doFold(cm, pos, options, force) {
  function makeWidget (line 61) | function makeWidget(cm, options) {
  function getOption (line 137) | function getOption(cm, options, name) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/foldgutter.js
  function State (line 39) | function State(options) {
  function parseOptions (line 44) | function parseOptions(opts) {
  function isFolded (line 52) | function isFolded(cm, line) {
  function marker (line 58) | function marker(spec) {
  function updateFoldInfo (line 68) | function updateFoldInfo(cm, from, to) {
  function updateInViewport (line 87) | function updateInViewport(cm) {
  function onGutterClick (line 96) | function onGutterClick(cm, line, gutter) {
  function onChange (line 104) | function onChange(cm) {
  function onViewportChange (line 113) | function onViewportChange(cm) {
  function onFold (line 137) | function onFold(cm, from) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/markdown-fold.js
  function isHeader (line 17) | function isHeader(lineNo) {
  function headerLevel (line 22) | function headerLevel(lineNo, line, nextLine) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/xml-fold.js
  function cmp (line 15) | function cmp(a, b) { return a.line - b.line || a.ch - b.ch; }
  function Iter (line 21) | function Iter(cm, line, ch, range) {
  function tagAt (line 28) | function tagAt(iter, ch) {
  function nextLine (line 33) | function nextLine(iter) {
  function prevLine (line 39) | function prevLine(iter) {
  function toTagEnd (line 46) | function toTagEnd(iter) {
  function toTagStart (line 57) | function toTagStart(iter) {
  function toNextTag (line 69) | function toNextTag(iter) {
  function toPrevTag (line 79) | function toPrevTag(iter) {
  function findMatchingClose (line 91) | function findMatchingClose(iter, tag) {
  function findMatchingOpen (line 112) | function findMatchingOpen(iter, tag) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/css-hint.js
  function add (line 31) | function add(keywords) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/html-hint.js
  function populate (line 332) | function populate(obj) {
  function htmlHint (line 342) | function htmlHint(cm, options) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/javascript-hint.js
  function forEach (line 14) | function forEach(arr, f) {
  function arrayContains (line 18) | function arrayContains(arr, item) {
  function scriptHint (line 31) | function scriptHint(editor, keywords, getToken, options) {
  function javascriptHint (line 60) | function javascriptHint(editor, options) {
  function getCoffeeScriptToken (line 67) | function getCoffeeScriptToken(editor, cur) {
  function coffeescriptHint (line 85) | function coffeescriptHint(editor, options) {
  function getCompletions (line 100) | function getCompletions(token, context, keywords, options) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/show-hint.js
  function retrieveHints (line 28) | function retrieveHints(getter, cm, options, then) {
  function Completion (line 52) | function Completion(cm, options) {
  function done (line 103) | function done() {
  function update (line 111) | function update() {
  function finishUpdate (line 116) | function finishUpdate(data_) {
  function clearDebounce (line 124) | function clearDebounce() {
  function activity (line 131) | function activity() {
  function getText (line 159) | function getText(completion) {
  function buildKeyMap (line 164) | function buildKeyMap(completion, handle) {
  function getHintElement (line 199) | function getHintElement(hintsElement, el) {
  function Widget (line 206) | function Widget(completion, data) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/sql-hint.js
  function getKeywords (line 23) | function getKeywords(editor) {
  function getText (line 29) | function getText(item) {
  function getItem (line 33) | function getItem(list, item) {
  function shallowClone (line 39) | function shallowClone(object) {
  function match (line 46) | function match(string, word) {
  function addMatches (line 52) | function addMatches(result, search, wordlist, formatter) {
  function cleanName (line 64) | function cleanName(name) {
  function insertBackticks (line 72) | function insertBackticks(name) {
  function nameCompletion (line 83) | function nameCompletion(cur, token, result, editor) {
  function eachWord (line 141) | function eachWord(lineText, f) {
  function convertCurToNumber (line 150) | function convertCurToNumber(cur) {
  function convertNumberToCur (line 155) | function convertNumberToCur(num) {
  function findTableByAlias (line 159) | function findTableByAlias(alias, editor) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/xml-hint.js
  function getHints (line 16) | function getHints(cm, options) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/lint/javascript-lint.js
  function validator (line 24) | function validator(text, options) {
  function cleanup (line 34) | function cleanup(error) {
  function fixWith (line 42) | function fixWith(error, fixes, severity, force) {
  function isBogus (line 62) | function isBogus(error) {
  function parseErrors (line 72) | function parseErrors(errors, output) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/lint/lint.js
  function showTooltip (line 15) | function showTooltip(e, content) {
  function rm (line 31) | function rm(elt) {
  function hideTooltip (line 34) | function hideTooltip(tt) {
  function showTooltipFor (line 41) | function showTooltipFor(e, content, node) {
  function LintState (line 58) | function LintState(cm, options, hasGutter) {
  function parseOptions (line 66) | function parseOptions(cm, options) {
  function clearMarks (line 74) | function clearMarks(cm) {
  function makeMarker (line 82) | function makeMarker(labels, severity, multiple, tooltips) {
  function getMaxSeverity (line 97) | function getMaxSeverity(a, b) {
  function groupByLine (line 102) | function groupByLine(annotations) {
  function annotationTooltip (line 111) | function annotationTooltip(ann) {
  function startLinting (line 120) | function startLinting(cm) {
  function updateLinting (line 129) | function updateLinting(cm, annotationsNotSorted) {
  function onChange (line 164) | function onChange(cm) {
  function popupSpanTooltip (line 170) | function popupSpanTooltip(ann, e) {
  function onMouseOver (line 175) | function onMouseOver(cm, e) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/merge/merge.js
  function DiffView (line 18) | function DiffView(mv, type) {
  function ensureDiff (line 60) | function ensureDiff(dv) {
  function registerUpdate (line 70) | function registerUpdate(dv) {
  function registerScroll (line 126) | function registerScroll(dv) {
  function syncScroll (line 135) | function syncScroll(dv, type) {
  function getOffsets (line 177) | function getOffsets(editor, around) {
  function setScrollLock (line 184) | function setScrollLock(dv, val, action) {
  function clearMarks (line 192) | function clearMarks(editor, arr, classes) {
  function updateMarks (line 207) | function updateMarks(editor, diff, state, type, classes) {
  function markChanges (line 227) | function markChanges(editor, diff, type, marks, from, to, classes) {
  function makeConnections (line 274) | function makeConnections(dv) {
  function getMatchingOrigLine (line 294) | function getMatchingOrigLine(editLine, chunks) {
  function findAlignedLines (line 306) | function findAlignedLines(dv, other) {
  function alignChunks (line 331) | function alignChunks(dv, force) {
  function alignLines (line 363) | function alignLines(cm, lines, aligners) {
  function padAbove (line 377) | function padAbove(cm, line, size) {
  function drawConnectorsForChunk (line 389) | function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) {
  function copyChunk (line 426) | function copyChunk(dv, to, from, chunk) {
  function buildGap (line 495) | function buildGap(dv) {
  function asString (line 541) | function asString(obj) {
  function getDiff (line 549) | function getDiff(a, b) {
  function getChunks (line 565) | function getChunks(diff) {
  function endOfLineClean (line 592) | function endOfLineClean(diff, i) {
  function startOfLineClean (line 601) | function startOfLineClean(diff, i) {
  function chunkBoundariesAround (line 610) | function chunkBoundariesAround(chunks, n, nInEdit) {
  function collapseSingle (line 626) | function collapseSingle(cm, from, to) {
  function collapseStretch (line 645) | function collapseStretch(size, editors) {
  function unclearNearChunks (line 659) | function unclearNearChunks(dv, margin, off, clear) {
  function collapseIdenticalStretches (line 669) | function collapseIdenticalStretches(mv, margin) {
  function elt (line 693) | function elt(tag, content, className, style) {
  function clear (line 702) | function clear(node) {
  function attrs (line 707) | function attrs(elt) {
  function copyObj (line 712) | function copyObj(obj, target) {
  function moveOver (line 718) | function moveOver(pos, str, copy, other) {
  function posMin (line 732) | function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a ...
  function posMax (line 733) | function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a ...
  function posEq (line 734) | function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/mode/loadmode.js
  function splitCallback (line 15) | function splitCallback(cont, n) {
  function ensureDeps (line 19) | function ensureDeps(mode, cont) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/mode/multiplex.js
  function indexOf (line 19) | function indexOf(string, pattern, from) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/mode/multiplex_test.js
  function MT (line 22) | function MT(name) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/mode/simple.js
  function ensureState (line 61) | function ensureState(states, name) {
  function toRegex (line 66) | function toRegex(val, caret) {
  function asToken (line 78) | function asToken(val) {
  function Rule (line 87) | function Rule(data, states) {
  function tokenFunction (line 94) | function tokenFunction(states, config) {
  function cmp (line 155) | function cmp(a, b) {
  function enterLocalMode (line 167) | function enterLocalMode(config, state, spec, token) {
  function indexOf (line 183) | function indexOf(val, arr) {
  function indentFunction (line 187) | function indentFunction(states, meta) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/runmode/colorize.js
  function textContent (line 16) | function textContent(node, out) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/runmode/runmode-standalone.js
  function splitLines (line 9) | function splitLines(string){ return string.split(/\r?\n|\r/); }
  function StringStream (line 11) | function StringStream(string) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/runmode/runmode.node.js
  function splitLines (line 8) | function splitLines(string){ return string.split(/\r?\n|\r/); }
  function StringStream (line 10) | function StringStream(string) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/scroll/annotatescrollbar.js
  function Annotation (line 21) | function Annotation(cm, options) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/scroll/scrollpastend.js
  function onChange (line 28) | function onChange(cm, change) {
  function updateBottomMargin (line 33) | function updateBottomMargin(cm) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/scroll/simplescrollbars.js
  function Bar (line 14) | function Bar(cls, orientation, scroll) {
  function SimpleScrollbars (line 84) | function SimpleScrollbars(cls, place, scroll) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/search/match-highlighter.js
  function State (line 36) | function State(options) {
  function cursorActivity (line 66) | function cursorActivity(cm) {
  function highlightMatches (line 72) | function highlightMatches(cm) {
  function isWord (line 97) | function isWord(cm, from, to) {
  function boundariesAround (line 114) | function boundariesAround(stream, re) {
  function makeOverlay (line 119) | function makeOverlay(query, hasBoundary, style) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/search/matchesonscrollbar.js
  function SearchAnnotation (line 20) | function SearchAnnotation(cm, query, caseFold, options) {
  function offsetLine (line 58) | function offsetLine(line, changeStart, sizeChange) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/search/search.js
  function searchOverlay (line 21) | function searchOverlay(query, caseInsensitive) {
  function SearchState (line 41) | function SearchState() {
  function getSearchState (line 45) | function getSearchState(cm) {
  function queryCaseInsensitive (line 48) | function queryCaseInsensitive(query) {
  function getSearchCursor (line 51) | function getSearchCursor(cm, query, pos) {
  function dialog (line 55) | function dialog(cm, text, shortText, deflt, f) {
  function confirmDialog (line 59) | function confirmDialog(cm, text, shortText, fs) {
  function parseQuery (line 63) | function parseQuery(query) {
  function doSearch (line 75) | function doSearch(cm, rev) {
  function findNext (line 94) | function findNext(cm, rev) {cm.operation(function() {
  function clearSearch (line 105) | function clearSearch(cm) {cm.operation(function() {
  function replace (line 117) | function replace(cm, all) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/search/searchcursor.js
  function SearchCursor (line 15) | function SearchCursor(doc, query, pos, caseFold) {
  function savePosAndFail (line 124) | function savePosAndFail(line) {
  function adjustPos (line 162) | function adjustPos(orig, folded, pos) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/selection/active-line.js
  function clearActiveLines (line 35) | function clearActiveLines(cm) {
  function sameArray (line 42) | function sameArray(a, b) {
  function updateActiveLines (line 49) | function updateActiveLines(cm, ranges) {
  function selectionChange (line 68) | function selectionChange(cm, sel) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/selection/mark-selection.js
  function onCursorActivity (line 36) | function onCursorActivity(cm) {
  function onChange (line 40) | function onChange(cm) {
  function coverRange (line 49) | function coverRange(cm, from, to, addAt) {
  function clear (line 65) | function clear(cm) {
  function reset (line 71) | function reset(cm) {
  function update (line 78) | function update(cm) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/selection/selection-pointer.js
  function mousemove (line 43) | function mousemove(cm, event) {
  function mouseout (line 54) | function mouseout(cm, event) {
  function reset (line 62) | function reset(cm) {
  function scheduleUpdate (line 67) | function scheduleUpdate(cm) {
  function update (line 77) | function update(cm) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/tern/tern.js
  function getFile (line 147) | function getFile(ts, name, c) {
  function findDoc (line 157) | function findDoc(ts, doc, name) {
  function resolveDoc (line 169) | function resolveDoc(ts, id) {
  function trackChange (line 175) | function trackChange(ts, doc, change) {
  function sendDoc (line 195) | function sendDoc(ts, doc) {
  function hint (line 204) | function hint(ts, cm, c) {
  function typeToIcon (line 239) | function typeToIcon(type) {
  function showContextInfo (line 251) | function showContextInfo(ts, cm, pos, queryName, c) {
  function updateArgHints (line 274) | function updateArgHints(ts, cm) {
  function showArgHints (line 316) | function showArgHints(ts, cm, pos) {
  function parseFnType (line 337) | function parseFnType(text) {
  function jumpToDef (line 370) | function jumpToDef(ts, cm) {
  function jumpBack (line 398) | function jumpBack(ts, cm) {
  function moveTo (line 404) | function moveTo(ts, curDoc, doc, start, end) {
  function findContext (line 413) | function findContext(doc, data) {
  function atInterestingExpression (line 443) | function atInterestingExpression(cm) {
  function rename (line 451) | function rename(ts, cm) {
  function selectName (line 462) | function selectName(ts, cm) {
  function applyChanges (line 480) | function applyChanges(ts, changes) {
  function buildRequest (line 500) | function buildRequest(ts, doc, query, pos) {
  function getFragmentAround (line 542) | function getFragmentAround(data, start, end) {
  function elt (line 573) | function elt(tagname, cls /*, ... elts*/) {
  function dialog (line 584) | function dialog(cm, text, f) {
  function tempTooltip (line 593) | function tempTooltip(cm, content) {
  function makeTooltip (line 623) | function makeTooltip(x, y, content) {
  function remove (line 631) | function remove(node) {
  function fadeOut (line 636) | function fadeOut(tooltip) {
  function showError (line 641) | function showError(ts, cm, msg) {
  function closeArgHints (line 648) | function closeArgHints(ts) {
  function docValue (line 652) | function docValue(ts, doc) {
  function WorkerServer (line 660) | function WorkerServer(ts) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/tern/worker.js
  function getFile (line 26) | function getFile(file, c) {
  function startServer (line 31) | function startServer(defs, plugins, scripts) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/addon/wrap/hardwrap.js
  function findParagraph (line 16) | function findParagraph(cm, pos, options) {
  function findBreakPoint (line 32) | function findBreakPoint(text, column, wrapOn, killTrailingSpace) {
  function wrapRange (line 42) | function wrapRange(cm, from, to, options) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/lib/codemirror.js
  function CodeMirror (line 59) | function CodeMirror(place, options) {
  function Display (line 130) | function Display(place, doc, input) {
  function loadMode (line 232) | function loadMode(cm) {
  function resetModeState (line 237) | function resetModeState(cm) {
  function wrappingChanged (line 248) | function wrappingChanged(cm) {
  function estimateHeight (line 266) | function estimateHeight(cm) {
  function estimateLineHeights (line 284) | function estimateLineHeights(cm) {
  function themeChanged (line 292) | function themeChanged(cm) {
  function guttersChanged (line 298) | function guttersChanged(cm) {
  function updateGutters (line 306) | function updateGutters(cm) {
  function updateGutterSpace (line 321) | function updateGutterSpace(cm) {
  function lineLength (line 329) | function lineLength(line) {
  function findMaxLine (line 348) | function findMaxLine(cm) {
  function setGuttersForLineNumbers (line 364) | function setGuttersForLineNumbers(options) {
  function measureForScrollbars (line 378) | function measureForScrollbars(cm) {
  function NativeScrollbars (line 394) | function NativeScrollbars(place, scroll, cm) {
  function NullScrollbars (line 473) | function NullScrollbars() {}
  function initScrollbars (line 484) | function initScrollbars(cm) {
  function updateScrollbars (line 506) | function updateScrollbars(cm, measure) {
  function updateScrollbarsInner (line 520) | function updateScrollbarsInner(cm, measure) {
  function visibleLines (line 542) | function visibleLines(display, doc, viewport) {
  function alignHorizontally (line 567) | function alignHorizontally(cm) {
  function maybeUpdateLineNumberWidth (line 586) | function maybeUpdateLineNumberWidth(cm) {
  function lineNumberFor (line 604) | function lineNumberFor(options, i) {
  function compensateForHScroll (line 611) | function compensateForHScroll(display) {
  function DisplayUpdate (line 617) | function DisplayUpdate(cm, viewport, force) {
  function maybeClipScrollbars (line 641) | function maybeClipScrollbars(cm) {
  function updateDisplayIfNeeded (line 655) | function updateDisplayIfNeeded(cm, update) {
  function postUpdateDisplay (line 727) | function postUpdateDisplay(cm, update) {
  function updateDisplaySimple (line 758) | function updateDisplaySimple(cm, viewport) {
  function setDocumentHeight (line 771) | function setDocumentHeight(cm, measure) {
  function updateHeightsInViewport (line 780) | function updateHeightsInViewport(cm) {
  function updateWidgetHeight (line 807) | function updateWidgetHeight(line) {
  function getDimensions (line 814) | function getDimensions(cm) {
  function patchDisplay (line 832) | function patchDisplay(cm, updateNumbersFrom, dims) {
  function updateLineForChanges (line 877) | function updateLineForChanges(cm, lineView, lineN, dims) {
  function ensureLineWrapped (line 890) | function ensureLineWrapped(lineView) {
  function updateLineBackground (line 901) | function updateLineBackground(lineView) {
  function getLineContent (line 915) | function getLineContent(cm, lineView) {
  function updateLineText (line 928) | function updateLineText(cm, lineView) {
  function updateLineClasses (line 943) | function updateLineClasses(lineView) {
  function updateLineGutter (line 953) | function updateLineGutter(cm, lineView, lineN, dims) {
  function updateLineWidgets (line 983) | function updateLineWidgets(cm, lineView, dims) {
  function buildLineElement (line 994) | function buildLineElement(cm, lineView, lineN, dims) {
  function insertLineWidgets (line 1008) | function insertLineWidgets(cm, lineView, dims) {
  function insertLineWidgetsFor (line 1014) | function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {
  function positionLineWidget (line 1030) | function positionLineWidget(widget, node, lineView, dims) {
  function copyPos (line 1060) | function copyPos(x) {return Pos(x.line, x.ch);}
  function maxPos (line 1061) | function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; }
  function minPos (line 1062) | function minPos(a, b) { return cmp(a, b) < 0 ? a : b; }
  function ensureFocus (line 1066) | function ensureFocus(cm) {
  function isReadOnly (line 1070) | function isReadOnly(cm) {
  function applyTextInput (line 1079) | function applyTextInput(cm, inserted, deleted, sel) {
  function copyableRanges (line 1132) | function copyableRanges(cm) {
  function disableBrowserMagic (line 1143) | function disableBrowserMagic(field) {
  function TextareaInput (line 1151) | function TextareaInput(cm) {
  function hiddenTextarea (line 1169) | function hiddenTextarea() {
  function prepareCopyCut (line 1222) | function prepareCopyCut(e) {
  function p (line 1344) | function p() {
  function prepareSelectAllHack (line 1440) | function prepareSelectAllHack() {
  function rehide (line 1451) | function rehide() {
  function ContentEditableInput (line 1490) | function ContentEditableInput(cm) {
  function onCopyCut (line 1551) | function onCopyCut(e) {
  function poll (line 1665) | function poll() {
  function posToDOM (line 1775) | function posToDOM(cm, pos) {
  function badPos (line 1791) | function badPos(pos, bad) { if (bad) pos.bad = true; return pos; }
  function domToPos (line 1793) | function domToPos(cm, node, offset) {
  function locateNodeInLineView (line 1812) | function locateNodeInLineView(lineView, node, offset) {
  function domTextBetween (line 1867) | function domTextBetween(cm, from, to, fromLine, toLine) {
  function Selection (line 1917) | function Selection(ranges, primIndex) {
  function Range (line 1954) | function Range(anchor, head) {
  function normalizeSelection (line 1969) | function normalizeSelection(ranges, primIndex) {
  function simpleSelection (line 1985) | function simpleSelection(anchor, head) {
  function clipLine (line 1991) | function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.fi...
  function clipPos (line 1992) | function clipPos(doc, pos) {
  function clipToLen (line 1998) | function clipToLen(pos, linelen) {
  function isLine (line 2004) | function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.si...
  function clipPosArray (line 2005) | function clipPosArray(doc, array) {
  function extendRange (line 2020) | function extendRange(doc, range, head, other) {
  function extendSelection (line 2039) | function extendSelection(doc, head, other, options) {
  function extendSelections (line 2045) | function extendSelections(doc, heads, options) {
  function replaceOneSelection (line 2053) | function replaceOneSelection(doc, i, range, options) {
  function setSimpleSelection (line 2060) | function setSimpleSelection(doc, anchor, head, options) {
  function filterSelectionChange (line 2066) | function filterSelectionChange(doc, sel) {
  function setSelectionReplaceHistory (line 2082) | function setSelectionReplaceHistory(doc, sel, options) {
  function setSelection (line 2093) | function setSelection(doc, sel, options) {
  function setSelectionNoUndo (line 2098) | function setSelectionNoUndo(doc, sel, options) {
  function setSelectionInner (line 2110) | function setSelectionInner(doc, sel) {
  function reCheckSelection (line 2124) | function reCheckSelection(doc) {
  function skipAtomicInSelection (line 2130) | function skipAtomicInSelection(doc, sel, bias, mayClear) {
  function skipAtomic (line 2145) | function skipAtomic(doc, pos, bias, mayClear) {
  function updateSelection (line 2197) | function updateSelection(cm) {
  function prepareSelection (line 2201) | function prepareSelection(cm, primary) {
  function drawSelectionCursor (line 2219) | function drawSelectionCursor(cm, range, output) {
  function drawSelectionRange (line 2238) | function drawSelectionRange(cm, range, output) {
  function restartBlink (line 2313) | function restartBlink(cm) {
  function startWorker (line 2329) | function startWorker(cm, time) {
  function highlightWorker (line 2334) | function highlightWorker(cm) {
  function findStartLine (line 2376) | function findStartLine(cm, n, precise) {
  function getStateBefore (line 2392) | function getStateBefore(cm, n, precise) {
  function paddingTop (line 2410) | function paddingTop(display) {return display.lineSpace.offsetTop;}
  function paddingVert (line 2411) | function paddingVert(display) {return display.mover.offsetHeight - displ...
  function paddingH (line 2412) | function paddingH(display) {
  function scrollGap (line 2421) | function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; }
  function displayWidth (line 2422) | function displayWidth(cm) {
  function displayHeight (line 2425) | function displayHeight(cm) {
  function ensureLineHeights (line 2433) | function ensureLineHeights(cm, lineView, rect) {
  function mapFromLineView (line 2454) | function mapFromLineView(lineView, line, lineN) {
  function updateExternalMeasurement (line 2467) | function updateExternalMeasurement(cm, line) {
  function measureChar (line 2480) | function measureChar(cm, line, ch, bias) {
  function findViewForLine (line 2485) | function findViewForLine(cm, lineN) {
  function prepareMeasureForLine (line 2498) | function prepareMeasureForLine(cm, line) {
  function measureCharPrepared (line 2518) | function measureCharPrepared(cm, prepared, ch, bias, varHeight) {
  function nodeAndOffsetInLineMap (line 2540) | function nodeAndOffsetInLineMap(map, ch, bias) {
  function measureCharInner (line 2577) | function measureCharInner(cm, prepared, ch, bias) {
  function maybeUpdateRectForZooming (line 2636) | function maybeUpdateRectForZooming(measure, rect) {
  function clearLineMeasurementCacheFor (line 2646) | function clearLineMeasurementCacheFor(lineView) {
  function clearLineMeasurementCache (line 2655) | function clearLineMeasurementCache(cm) {
  function clearCaches (line 2662) | function clearCaches(cm) {
  function pageScrollX (line 2669) | function pageScrollX() { return window.pageXOffset || (document.document...
  function pageScrollY (line 2670) | function pageScrollY() { return window.pageYOffset || (document.document...
  function intoCoordSystem (line 2676) | function intoCoordSystem(cm, lineObj, rect, context) {
  function fromCoordSystem (line 2698) | function fromCoordSystem(cm, coords, context) {
  function charCoords (line 2715) | function charCoords(cm, pos, context, lineObj, bias) {
  function cursorCoords (line 2723) | function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHei...
  function estimateCoords (line 2755) | function estimateCoords(cm, pos) {
  function PosWithInfo (line 2769) | function PosWithInfo(line, ch, outside, xRel) {
  function coordsChar (line 2778) | function coordsChar(cm, x, y) {
  function coordsCharInner (line 2799) | function coordsCharInner(cm, lineObj, lineNo, x, y) {
  function textHeight (line 2841) | function textHeight(display) {
  function charWidth (line 2861) | function charWidth(display) {
  function startOperation (line 2883) | function startOperation(cm) {
  function fireCallbacksForOps (line 2910) | function fireCallbacksForOps(group) {
  function endOperation (line 2927) | function endOperation(cm) {
  function endOperations (line 2942) | function endOperations(group) {
  function endOperation_R1 (line 2956) | function endOperation_R1(op) {
  function endOperation_W1 (line 2969) | function endOperation_W1(op) {
  function endOperation_R2 (line 2973) | function endOperation_R2(op) {
  function endOperation_W2 (line 2994) | function endOperation_W2(op) {
  function endOperation_finish (line 3017) | function endOperation_finish(op) {
  function runInOp (line 3064) | function runInOp(cm, f) {
  function operation (line 3071) | function operation(cm, f) {
  function methodOp (line 3081) | function methodOp(f) {
  function docMethodOp (line 3089) | function docMethodOp(f) {
  function LineView (line 3104) | function LineView(doc, line, lineN) {
  function buildViewArray (line 3116) | function buildViewArray(cm, from, to) {
  function regChange (line 3132) | function regChange(cm, from, to, lendiff) {
  function regLineChange (line 3197) | function regLineChange(cm, line, type) {
  function resetView (line 3211) | function resetView(cm) {
  function findViewIndex (line 3219) | function findViewIndex(cm, n) {
  function viewCuttingPoint (line 3230) | function viewCuttingPoint(cm, oldN, newN, dir) {
  function adjustView (line 3256) | function adjustView(cm, from, to) {
  function countDirtyView (line 3277) | function countDirtyView(cm) {
  function registerEventHandlers (line 3289) | function registerEventHandlers(cm) {
  function onResize (line 3398) | function onResize(cm) {
  function eventInWidget (line 3411) | function eventInWidget(display, e) {
  function posFromMouse (line 3424) | function posFromMouse(cm, e, liberal, forRect) {
  function onMouseDown (line 3445) | function onMouseDown(e) {
  function leftButtonDown (line 3483) | function leftButtonDown(cm, e, start) {
  function leftButtonStartDrag (line 3509) | function leftButtonStartDrag(cm, e, start, modifier) {
  function leftButtonSelect (line 3536) | function leftButtonSelect(cm, e, start, type, addNew) {
  function gutterEvent (line 3678) | function gutterEvent(cm, e, type, prevent, signalfn) {
  function clickInGutter (line 3701) | function clickInGutter(cm, e) {
  function onDrop (line 3709) | function onDrop(e) {
  function onDragStart (line 3759) | function onDragStart(cm, e) {
  function setScrollTop (line 3785) | function setScrollTop(cm, val) {
  function setScrollLeft (line 3796) | function setScrollLeft(cm, val, isScroller) {
  function onScrollWheel (line 3840) | function onScrollWheel(cm, e) {
  function doHandleBinding (line 3912) | function doHandleBinding(cm, bound, dropShift) {
  function lookupKeyForEditor (line 3932) | function lookupKeyForEditor(cm, name, handle) {
  function dispatchKey (line 3942) | function dispatchKey(cm, name, e, handle) {
  function handleKeyBinding (line 3974) | function handleKeyBinding(cm, e) {
  function handleCharBinding (line 3993) | function handleCharBinding(cm, e, ch) {
  function onKeyDown (line 3999) | function onKeyDown(e) {
  function showCrossHair (line 4020) | function showCrossHair(cm) {
  function onKeyUp (line 4035) | function onKeyUp(e) {
  function onKeyPress (line 4040) | function onKeyPress(e) {
  function onFocus (line 4053) | function onFocus(cm) {
  function onBlur (line 4070) | function onBlur(cm) {
  function onContextMenu (line 4085) | function onContextMenu(cm, e) {
  function contextMenuInGutter (line 4090) | function contextMenuInGutter(cm, e) {
  function adjustForChange (line 4107) | function adjustForChange(pos, change) {
  function computeSelAfterChange (line 4116) | function computeSelAfterChange(doc, change) {
  function offsetPos (line 4126) | function offsetPos(pos, old, nw) {
  function computeReplacedSel (line 4135) | function computeReplacedSel(doc, changes, hint) {
  function filterChange (line 4155) | function filterChange(doc, change, update) {
  function makeChange (line 4179) | function makeChange(doc, change, ignoreReadOnly) {
  function makeChangeInner (line 4201) | function makeChangeInner(doc, change) {
  function makeChangeFromHistory (line 4219) | function makeChangeFromHistory(doc, type, allowSelectionOnly) {
  function shiftDoc (line 4285) | function shiftDoc(doc, distance) {
  function makeChangeSingleDoc (line 4301) | function makeChangeSingleDoc(doc, change, selAfter, spans) {
  function makeChangeSingleDocInEditor (line 4334) | function makeChangeSingleDocInEditor(cm, change, spans) {
  function replaceRange (line 4393) | function replaceRange(doc, code, from, to, origin) {
  function maybeScrollWindow (line 4404) | function maybeScrollWindow(cm, coords) {
  function scrollPosIntoView (line 4424) | function scrollPosIntoView(cm, pos, end, margin) {
  function scrollIntoView (line 4448) | function scrollIntoView(cm, x1, y1, x2, y2) {
  function calculateScrollPos (line 4458) | function calculateScrollPos(cm, x1, y1, x2, y2) {
  function addToScrollPos (line 4488) | function addToScrollPos(cm, left, top) {
  function ensureCursorVisible (line 4498) | function ensureCursorVisible(cm) {
  function resolveScrollToPos (line 4512) | function resolveScrollToPos(cm) {
  function indentLine (line 4532) | function indentLine(cm, n, how, aggressive) {
  function changeLine (line 4593) | function changeLine(doc, handle, changeType, op) {
  function deleteNearSelection (line 4604) | function deleteNearSelection(cm, compute) {
  function findPosH (line 4636) | function findPosH(doc, pos, dir, unit, visually) {
  function findPosV (line 4687) | function findPosV(cm, pos, dir, unit) {
  function interpret (line 5106) | function interpret(val) {
  function option (line 5162) | function option(name, deflt, handle, notOnInit) {
  function normalizeKeyName (line 5608) | function normalizeKeyName(name) {
  function getKeyMap (line 5694) | function getKeyMap(val) {
  function save (line 5715) | function save() {textarea.value = cm.getValue();}
  function markText (line 5965) | function markText(doc, from, to, options, type) {
  function markTextShared (line 6058) | function markTextShared(doc, from, to, options, type) {
  function findSharedMarkers (line 6073) | function findSharedMarkers(doc) {
  function copySharedMarkers (line 6078) | function copySharedMarkers(doc, markers) {
  function detachSharedMarkers (line 6090) | function detachSharedMarkers(markers) {
  function MarkedSpan (line 6106) | function MarkedSpan(marker, from, to) {
  function getMarkedSpanFor (line 6112) | function getMarkedSpanFor(spans, marker) {
  function removeMarkedSpan (line 6120) | function removeMarkedSpan(spans, span) {
  function addMarkedSpan (line 6126) | function addMarkedSpan(line, span) {
  function markedSpansBefore (line 6135) | function markedSpansBefore(old, startCh, isInsert) {
  function markedSpansAfter (line 6146) | function markedSpansAfter(old, endCh, isInsert) {
  function stretchSpansOverChange (line 6165) | function stretchSpansOverChange(doc, change) {
  function clearEmptySpans (line 6227) | function clearEmptySpans(spans) {
  function mergeOldSpans (line 6241) | function mergeOldSpans(doc, change) {
  function removeReadOnlyRanges (line 6264) | function removeReadOnlyRanges(doc, from, to) {
  function detachMarkedSpans (line 6293) | function detachMarkedSpans(line) {
  function attachMarkedSpans (line 6300) | function attachMarkedSpans(line, spans) {
  function extraLeft (line 6309) | function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; }
  function extraRight (line 6310) | function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; }
  function compareCollapsedMarkers (line 6315) | function compareCollapsedMarkers(a, b) {
  function collapsedSpanAtSide (line 6328) | function collapsedSpanAtSide(line, start) {
  function collapsedSpanAtStart (line 6338) | function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, t...
  function collapsedSpanAtEnd (line 6339) | function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, fal...
  function conflictingCollapsedRange (line 6344) | function conflictingCollapsedRange(doc, lineNo, from, to, marker) {
  function visualLine (line 6364) | function visualLine(line) {
  function visualLineContinued (line 6373) | function visualLineContinued(line) {
  function visualLineNo (line 6384) | function visualLineNo(doc, lineN) {
  function visualLineEndNo (line 6391) | function visualLineEndNo(doc, lineN) {
  function lineIsHidden (line 6403) | function lineIsHidden(doc, line) {
  function lineIsHiddenInner (line 6414) | function lineIsHiddenInner(doc, line, span) {
  function adjustScrollWhenAboveVisible (line 6442) | function adjustScrollWhenAboveVisible(cm, line, diff) {
  function widgetHeight (line 6471) | function widgetHeight(widget) {
  function addLineWidget (line 6484) | function addLineWidget(cm, handle, node, options) {
  function updateLine (line 6518) | function updateLine(line, text, markedSpans, estimateHeight) {
  function cleanUpLine (line 6530) | function cleanUpLine(line) {
  function extractLineClasses (line 6535) | function extractLineClasses(type, output) {
  function callBlankLine (line 6549) | function callBlankLine(mode, state) {
  function readToken (line 6556) | function readToken(mode, stream, state, inner) {
  function takeToken (line 6566) | function takeToken(cm, pos, precise, asArray) {
  function runMode (line 6588) | function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) {
  function highlightLine (line 6629) | function highlightLine(cm, line, state, forceToEnd) {
  function getLineStyles (line 6667) | function getLineStyles(cm, line, updateFrontier) {
  function processLine (line 6681) | function processLine(cm, text, state, startAt) {
  function interpretTokenStyle (line 6696) | function interpretTokenStyle(style, options) {
  function buildLineContent (line 6708) | function buildLineContent(cm, lineView) {
  function defaultSpecialCharPlaceholder (line 6762) | function defaultSpecialCharPlaceholder(ch) {
  function buildToken (line 6771) | function buildToken(builder, text, style, startStyle, endStyle, title, c...
  function buildTokenSplitSpaces (line 6824) | function buildTokenSplitSpaces(inner) {
  function buildTokenBadBidi (line 6838) | function buildTokenBadBidi(inner, order) {
  function buildCollapsedSpan (line 6857) | function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
  function insertLineContent (line 6874) | function insertLineContent(line, builder, styles) {
  function isWholeLineUpdate (line 6939) | function isWholeLineUpdate(doc, change) {
  function updateDoc (line 6945) | function updateDoc(doc, change, markedSpans, estimateHeight) {
  function LeafChunk (line 7008) | function LeafChunk(lines) {
  function BranchChunk (line 7048) | function BranchChunk(children) {
  function linkedDocs (line 7501) | function linkedDocs(doc, f, sharedHistOnly) {
  function attachDoc (line 7516) | function attachDoc(cm, doc) {
  function getLine (line 7530) | function getLine(doc, n) {
  function getBetween (line 7545) | function getBetween(doc, start, end) {
  function getLines (line 7557) | function getLines(doc, from, to) {
  function updateLineHeight (line 7565) | function updateLineHeight(line, height) {
  function lineNo (line 7572) | function lineNo(line) {
  function lineAtHeight (line 7586) | function lineAtHeight(chunk, h) {
  function heightAtLine (line 7607) | function heightAtLine(lineObj) {
  function getOrder (line 7629) | function getOrder(line) {
  function History (line 7637) | function History(startGen) {
  function historyChangeFromChange (line 7654) | function historyChangeFromChange(doc, change) {
  function clearSelectionEvents (line 7663) | function clearSelectionEvents(array) {
  function lastChangeEvent (line 7673) | function lastChangeEvent(hist, force) {
  function addChangeToHistory (line 7688) | function addChangeToHistory(doc, change, selAfter, opId) {
  function selectionEventCanBeMerged (line 7730) | function selectionEventCanBeMerged(doc, origin, prev, sel) {
  function addSelectionToHistory (line 7743) | function addSelectionToHistory(doc, sel, opId, options) {
  function pushSelectionToHistory (line 7765) | function pushSelectionToHistory(sel, dest) {
  function attachLocalSpans (line 7772) | function attachLocalSpans(doc, change, from, to) {
  function removeClearedSpans (line 7783) | function removeClearedSpans(spans) {
  function getOldSpans (line 7793) | function getOldSpans(doc, change) {
  function copyHistoryArray (line 7803) | function copyHistoryArray(events, newGroup, instantiateSel) {
  function rebaseHistSelSingle (line 7828) | function rebaseHistSelSingle(pos, from, to, diff) {
  function rebaseHistArray (line 7844) | function rebaseHistArray(array, from, to, diff) {
  function rebaseHist (line 7872) | function rebaseHist(hist, change) {
  function e_defaultPrevented (line 7891) | function e_defaultPrevented(e) {
  function e_target (line 7896) | function e_target(e) {return e.target || e.srcElement;}
  function e_button (line 7897) | function e_button(e) {
  function signalLater (line 7954) | function signalLater(emitter, type /*, values...*/) {
  function fireOrphanDelayed (line 7971) | function fireOrphanDelayed() {
  function signalDOMEvent (line 7980) | function signalDOMEvent(cm, e, override) {
  function signalCursorActivity (line 7987) | function signalCursorActivity(cm) {
  function hasHandler (line 7995) | function hasHandler(emitter, type) {
  function eventMixin (line 8002) | function eventMixin(ctor) {
  function Delayed (line 8019) | function Delayed() {this.id = null;}
  function findColumn (line 8044) | function findColumn(string, goal, tabSize) {
  function spaceStr (line 8059) | function spaceStr(n) {
  function lst (line 8065) | function lst(arr) { return arr[arr.length-1]; }
  function indexOf (line 8073) | function indexOf(array, elt) {
  function map (line 8078) | function map(array, f) {
  function nothing (line 8084) | function nothing() {}
  function createObj (line 8086) | function createObj(base, props) {
  function copyObj (line 8098) | function copyObj(obj, target, overwrite) {
  function bind (line 8106) | function bind(f) {
  function isWordChar (line 8116) | function isWordChar(ch, helper) {
  function isEmpty (line 8122) | function isEmpty(obj) {
  function isExtendingChar (line 8133) | function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendi...
  function elt (line 8137) | function elt(tag, content, className, style) {
  function removeChildren (line 8163) | function removeChildren(e) {
  function removeChildrenAndAdd (line 8169) | function removeChildrenAndAdd(parent, e) {
  function activeElt (line 8184) | function activeElt() { return document.activeElement; }
  function classTest (line 8192) | function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)...
  function joinClasses (line 8205) | function joinClasses(a, b) {
  function forEachCodeMirror (line 8218) | function forEachCodeMirror(f) {
  function ensureGlobalHandlers (line 8228) | function ensureGlobalHandlers() {
  function registerGlobalHandlers (line 8233) | function registerGlobalHandlers() {
  function zeroWidthElement (line 8260) | function zeroWidthElement(measure) {
  function hasBadBidiRects (line 8275) | function hasBadBidiRects(measure) {
  function hasBadZoomedRects (line 8322) | function hasBadZoomedRects(measure) {
  function iterateBidiSections (line 8351) | function iterateBidiSections(order, from, to, f) {
  function bidiLeft (line 8364) | function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
  function bidiRight (line 8365) | function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
  function lineLeft (line 8367) | function lineLeft(line) { var order = getOrder(line); return order ? bid...
  function lineRight (line 8368) | function lineRight(line) {
  function lineStart (line 8374) | function lineStart(cm, lineN) {
  function lineEnd (line 8382) | function lineEnd(cm, lineN) {
  function lineStartSmart (line 8392) | function lineStartSmart(cm, pos) {
  function compareBidiLevel (line 8404) | function compareBidiLevel(order, a, b) {
  function getBidiPartAt (line 8411) | function getBidiPartAt(order, pos) {
  function moveInLine (line 8431) | function moveInLine(line, pos, dir, byUnit) {
  function moveVisually (line 8443) | function moveVisually(line, start, dir, byUnit) {
  function moveLogically (line 8466) | function moveLogically(line, start, dir, byUnit) {
  function charType (line 8500) | function charType(code) {
  function BidiSpan (line 8515) | function BidiSpan(level, from, to) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/asterisk/asterisk.js
  function basicToken (line 66) | function basicToken(stream,state){

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/clike/clike.js
  function tokenBase (line 29) | function tokenBase(stream, state) {
  function tokenString (line 75) | function tokenString(quote) {
  function tokenComment (line 88) | function tokenComment(stream, state) {
  function Context (line 100) | function Context(indented, column, type, align, prev) {
  function pushContext (line 107) | function pushContext(state, col, type) {
  function popContext (line 113) | function popContext(state) {
  function words (line 182) | function words(str) {
  function cppHook (line 191) | function cppHook(stream, state) {
  function cpp11StringHook (line 209) | function cpp11StringHook(stream, state) {
  function tokenAtString (line 234) | function tokenAtString(stream, state) {
  function tokenRawString (line 247) | function tokenRawString(stream, state) {
  function def (line 258) | function def(mimes, mode) {
  function tokenTripleString (line 350) | function tokenTripleString(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/clojure/clojure.js
  function makeKeywords (line 25) | function makeKeywords(str) {
  function stateStack (line 65) | function stateStack(indent, type, prev) { // represents a state stack ob...
  function pushStack (line 71) | function pushStack(state, indent, type) {
  function popStack (line 75) | function popStack(state) {
  function isNumber (line 79) | function isNumber(ch, stream){
  function eatCharacter (line 113) | function eatCharacter(stream) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/cobol/cobol.js
  function makeKeywords (line 22) | function makeKeywords(str) {
  function isNumber (line 150) | function isNumber(ch, stream){

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/coffeescript/coffeescript.js
  function wordRegexp (line 21) | function wordRegexp(words) {
  function tokenBase (line 50) | function tokenBase(stream, state) {
  function tokenFactory (line 177) | function tokenFactory(delimiter, singleline, outclass) {
  function longComment (line 204) | function longComment(stream, state) {
  function indent (line 216) | function indent(stream, state, type) {
  function dedent (line 240) | function dedent(stream, state) {
  function tokenLexer (line 264) | function tokenLexer(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/commonlisp/commonlisp.js
  function readSym (line 21) | function readSym(stream) {
  function base (line 30) | function base(stream, state) {
  function inString (line 63) | function inString(stream, state) {
  function inComment (line 72) | function inComment(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/css/css.js
  function ret (line 31) | function ret(style, tp) { type = tp; return style; }
  function tokenBase (line 35) | function tokenBase(stream, state) {
  function tokenString (line 90) | function tokenString(quote) {
  function tokenParenthesized (line 105) | function tokenParenthesized(stream, state) {
  function Context (line 116) | function Context(type, indent, prev) {
  function pushContext (line 122) | function pushContext(state, stream, type) {
  function popContext (line 127) | function popContext(state) {
  function pass (line 132) | function pass(type, stream, state) {
  function popAndPass (line 135) | function popAndPass(type, stream, state, n) {
  function wordAsValue (line 143) | function wordAsValue(stream) {
  function keySet (line 375) | function keySet(array) {
  function tokenCComment (line 642) | function tokenCComment(stream, state) {
  function tokenSGMLComment (line 654) | function tokenSGMLComment(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/css/less_test.js
  function MT (line 8) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/css/scss_test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/css/test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/d/d.js
  function tokenBase (line 27) | function tokenBase(stream, state) {
  function tokenString (line 77) | function tokenString(quote) {
  function tokenComment (line 90) | function tokenComment(stream, state) {
  function tokenNestedComment (line 102) | function tokenNestedComment(stream, state) {
  function Context (line 114) | function Context(indented, column, type, align, prev) {
  function pushContext (line 121) | function pushContext(state, col, type) {
  function popContext (line 127) | function popContext(state) {
  function words (line 189) | function words(str) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/dart/dart.js
  function set (line 23) | function set(words) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/django/django.js
  function tokenBase (line 24) | function tokenBase (stream, state) {
  function inTag (line 34) | function inTag (close) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/dtd/dtd.js
  function ret (line 23) | function ret(style, tp) {type = tp; return style;}
  function tokenBase (line 25) | function tokenBase(stream, state) {
  function tokenSGMLComment (line 54) | function tokenSGMLComment(stream, state) {
  function tokenString (line 66) | function tokenString(quote) {
  function inBlock (line 80) | function inBlock(style, terminator) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/dylan/dylan.js
  function chain (line 152) | function chain(stream, state, f) {
  function ret (line 159) | function ret(_type, style, _content) {
  function tokenBase (line 165) | function tokenBase(stream, state) {
  function tokenComment (line 250) | function tokenComment(stream, state) {
  function tokenString (line 263) | function tokenString(quote, type, style) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/ecl/ecl.js
  function words (line 16) | function words(str) {
  function metaHook (line 22) | function metaHook(stream, state) {
  function tokenBase (line 42) | function tokenBase(stream, state) {
  function tokenString (line 108) | function tokenString(quote) {
  function tokenComment (line 121) | function tokenComment(stream, state) {
  function Context (line 133) | function Context(indented, column, type, align, prev) {
  function pushContext (line 140) | function pushContext(state, col, type) {
  function popContext (line 143) | function popContext(state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/eiffel/eiffel.js
  function wordObj (line 15) | function wordObj(words) {
  function chain (line 89) | function chain(newtok, stream, state) {
  function tokenBase (line 94) | function tokenBase(stream, state) {
  function readQuoted (line 121) | function readQuoted(quote, style,  unescaped) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/erlang/erlang.js
  function tokenizer (line 103) | function tokenizer(stream,state) {
  function nongreedy (line 294) | function nongreedy(stream,re,words) {
  function greedy (line 308) | function greedy(stream,re,words) {
  function doubleQuote (line 325) | function doubleQuote(stream) {
  function singleQuote (line 329) | function singleQuote(stream) {
  function quote (line 333) | function quote(stream,quoteChar,escapeChar) {
  function lookahead (line 345) | function lookahead(stream) {
  function is_member (line 350) | function is_member(element,list) {
  function rval (line 354) | function rval(state,stream,type) {
  function aToken (line 388) | function aToken(tok,col,ind,typ) {
  function realToken (line 395) | function realToken(type,stream) {
  function fakeToken (line 402) | function fakeToken(type) {
  function peekToken (line 406) | function peekToken(state,depth) {
  function pushToken (line 417) | function pushToken(state,token) {
  function maybe_drop_pre (line 425) | function maybe_drop_pre(s,token) {
  function maybe_drop_post (line 439) | function maybe_drop_post(s) {
  function d (line 466) | function d(stack,tt) {
  function indenter (line 506) | function indenter(state,textAfter) {
  function wordafter (line 549) | function wordafter(str) {
  function postcommaToken (line 555) | function postcommaToken(state) {
  function defaultToken (line 562) | function defaultToken(state) {
  function getToken (line 576) | function getToken(state,tokens) {
  function getTokenIndex (line 583) | function getTokenIndex(objs,propname,propvals) {
  function truthy (line 593) | function truthy(x) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/forth/forth.js
  function toWordList (line 16) | function toWordList(words) {
  function searchWordList (line 67) | function searchWordList (wordList, word) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/fortran/fortran.js
  function words (line 15) | function words(array) {
  function tokenBase (line 117) | function tokenBase(stream, state) {
  function tokenString (line 155) | function tokenString(quote) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/gas/gas.js
  function x86 (line 144) | function x86(_parserConfig) {
  function armv6 (line 191) | function armv6(_parserConfig) {
  function nextUntilUnescaped (line 234) | function nextUntilUnescaped(stream, end) {
  function clikeComment (line 245) | function clikeComment(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/gfm/gfm.js
  function blankLine (line 16) | function blankLine(state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/gfm/test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...
  function FT (line 8) | function FT(name) { test.mode(name, modeHighlightFormatting, Array.proto...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/go/go.js
  function tokenBase (line 40) | function tokenBase(stream, state) {
  function tokenString (line 84) | function tokenString(quote) {
  function tokenComment (line 97) | function tokenComment(stream, state) {
  function Context (line 109) | function Context(indented, column, type, align, prev) {
  function pushContext (line 116) | function pushContext(state, col, type) {
  function popContext (line 119) | function popContext(state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/groovy/groovy.js
  function words (line 15) | function words(str) {
  function tokenBase (line 30) | function tokenBase(stream, state) {
  function startString (line 79) | function startString(quote, stream, state) {
  function tokenBaseUntilBrace (line 105) | function tokenBaseUntilBrace() {
  function tokenComment (line 123) | function tokenComment(stream, state) {
  function expectExpression (line 135) | function expectExpression(last) {
  function Context (line 140) | function Context(indented, column, type, align, prev) {
  function pushContext (line 147) | function pushContext(state, col, type) {
  function popContext (line 150) | function popContext(state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/haml/haml.js
  function rubyInQuote (line 19) | function rubyInQuote(endQuote) {
  function ruby (line 33) | function ruby(stream, state) {
  function html (line 41) | function html(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/haml/test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/haskell/haskell.js
  function switchState (line 16) | function switchState(source, setState, f) {
  function normal (line 32) | function normal(source, setState) {
  function ncomment (line 125) | function ncomment(type, nest) {
  function stringLiteral (line 149) | function stringLiteral(source, setState) {
  function stringGap (line 172) | function stringGap(source, setState) {
  function setType (line 184) | function setType(t) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/haxe/haxe.js
  function kw (line 20) | function kw(type) {return {type: type, style: "keyword"};}
  function chain (line 39) | function chain(stream, state, f) {
  function nextUntilUnescaped (line 44) | function nextUntilUnescaped(stream, end) {
  function ret (line 57) | function ret(tp, style, cont) {
  function haxeTokenBase (line 62) | function haxeTokenBase(stream, state) {
  function haxeTokenString (line 125) | function haxeTokenString(quote) {
  function haxeTokenComment (line 133) | function haxeTokenComment(stream, state) {
  function HaxeLexical (line 149) | function HaxeLexical(indented, column, type, align, prev, info) {
  function inScope (line 158) | function inScope(state, varname) {
  function parseHaxe (line 163) | function parseHaxe(state, style, type, content, stream) {
  function imported (line 185) | function imported(state, typename)
  function registerimport (line 195) | function registerimport(importname) {
  function pass (line 204) | function pass() {
  function cont (line 207) | function cont() {
  function register (line 211) | function register(varname) {
  function pushcontext (line 224) | function pushcontext() {
  function popcontext (line 228) | function popcontext() {
  function pushlex (line 232) | function pushlex(type, info) {
  function poplex (line 240) | function poplex() {
  function expect (line 250) | function expect(wanted) {
  function statement (line 259) | function statement(type) {
  function expression (line 281) | function expression(type) {
  function maybeexpression (line 291) | function maybeexpression(type) {
  function maybeoperator (line 296) | function maybeoperator(type, value) {
  function maybeattribute (line 305) | function maybeattribute(type) {
  function metadef (line 311) | function metadef(type) {
  function metaargs (line 316) | function metaargs(type) {
  function importdef (line 320) | function importdef (type, value) {
  function typedef (line 325) | function typedef (type, value)
  function maybelabel (line 331) | function maybelabel(type) {
  function property (line 335) | function property(type) {
  function objprop (line 338) | function objprop(type) {
  function commasep (line 342) | function commasep(what, end) {
  function block (line 353) | function block(type) {
  function vardef1 (line 357) | function vardef1(type, value) {
  function vardef2 (line 361) | function vardef2(type, value) {
  function forspec1 (line 365) | function forspec1(type, value) {
  function forin (line 371) | function forin(_type, value) {
  function functiondef (line 374) | function functiondef(type, value) {
  function typeuse (line 379) | function typeuse(type) {
  function typestring (line 382) | function typestring(type) {
  function typeprop (line 387) | function typeprop(type) {
  function funarg (line 390) | function funarg(type, value) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/htmlembedded/htmlembedded.js
  function htmlDispatch (line 24) | function htmlDispatch(stream, state) {
  function scriptingDispatch (line 34) | function scriptingDispatch(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/htmlmixed/htmlmixed.js
  function html (line 31) | function html(stream, state) {
  function maybeBackup (line 58) | function maybeBackup(stream, pat, style) {
  function script (line 68) | function script(stream, state) {
  function css (line 77) | function css(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/http/http.js
  function failFirstLine (line 15) | function failFirstLine(stream, state) {
  function start (line 21) | function start(stream, state) {
  function responseStatusCode (line 33) | function responseStatusCode(stream, state) {
  function responseStatusText (line 54) | function responseStatusText(stream, state) {
  function requestPath (line 60) | function requestPath(stream, state) {
  function requestProtocol (line 66) | function requestProtocol(stream, state) {
  function header (line 75) | function header(stream) {
  function body (line 89) | function body(stream) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/idl/idl.js
  function wordRegexp (line 14) | function wordRegexp(words) {
  function tokenBase (line 244) | function tokenBase(stream) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/jade/jade.js
  function State (line 29) | function State() {
  function javaScript (line 105) | function javaScript(stream, state) {
  function javaScriptArguments (line 122) | function javaScriptArguments(stream, state) {
  function yieldStatement (line 143) | function yieldStatement(stream) {
  function doctype (line 149) | function doctype(stream) {
  function interpolation (line 155) | function interpolation(stream, state) {
  function interpolationContinued (line 163) | function interpolationContinued(stream, state) {
  function caseStatement (line 179) | function caseStatement(stream, state) {
  function when (line 186) | function when(stream, state) {
  function defaultStatement (line 194) | function defaultStatement(stream) {
  function extendsStatement (line 200) | function extendsStatement(stream, state) {
  function append (line 207) | function append(stream, state) {
  function prepend (line 213) | function prepend(stream, state) {
  function block (line 219) | function block(stream, state) {
  function include (line 226) | function include(stream, state) {
  function includeFiltered (line 233) | function includeFiltered(stream, state) {
  function includeFilteredContinued (line 240) | function includeFilteredContinued(stream, state) {
  function mixin (line 249) | function mixin(stream, state) {
  function call (line 256) | function call(stream, state) {
  function callArguments (line 270) | function callArguments(stream, state) {
  function conditional (line 281) | function conditional(stream, state) {
  function each (line 288) | function each(stream, state) {
  function eachContinued (line 294) | function eachContinued(stream, state) {
  function whileStatement (line 309) | function whileStatement(stream, state) {
  function tag (line 316) | function tag(stream, state) {
  function filter (line 327) | function filter(stream, state) {
  function code (line 344) | function code(stream, state) {
  function id (line 351) | function id(stream) {
  function className (line 357) | function className(stream) {
  function attrs (line 363) | function attrs(stream, state) {
  function attrsContinued (line 375) | function attrsContinued(stream, state) {
  function attributesBlock (line 419) | function attributesBlock(stream, state) {
  function indent (line 427) | function indent(stream) {
  function comment (line 433) | function comment(stream, state) {
  function colon (line 441) | function colon(stream) {
  function text (line 447) | function text(stream, state) {
  function dot (line 459) | function dot(stream, state) {
  function fail (line 472) | function fail(stream) {
  function setInnerMode (line 478) | function setInnerMode(stream, state, mode) {
  function innerMode (line 491) | function innerMode(stream, state, force) {
  function restOfLine (line 511) | function restOfLine(stream, state) {
  function startState (line 525) | function startState() {
  function copyState (line 528) | function copyState(state) {
  function nextToken (line 537) | function nextToken(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/javascript/javascript.js
  function kw (line 27) | function kw(type) {return {type: type, style: "keyword"};}
  function readRegexp (line 73) | function readRegexp(stream) {
  function ret (line 88) | function ret(tp, style, cont) {
  function tokenBase (line 92) | function tokenBase(stream, state) {
  function tokenString (line 144) | function tokenString(quote) {
  function tokenComment (line 160) | function tokenComment(stream, state) {
  function tokenQuasi (line 172) | function tokenQuasi(stream, state) {
  function findFatArrow (line 192) | function findFatArrow(stream, state) {
  function JSLexical (line 222) | function JSLexical(indented, column, type, align, prev, info) {
  function inScope (line 231) | function inScope(state, varname) {
  function parseJS (line 240) | function parseJS(state, style, type, content, stream) {
  function pass (line 264) | function pass() {
  function cont (line 267) | function cont() {
  function register (line 271) | function register(varname) {
  function pushcontext (line 292) | function pushcontext() {
  function popcontext (line 296) | function popcontext() {
  function pushlex (line 300) | function pushlex(type, info) {
  function poplex (line 311) | function poplex() {
  function expect (line 321) | function expect(wanted) {
  function statement (line 330) | function statement(type, value) {
  function expression (line 356) | function expression(type) {
  function expressionNoComma (line 359) | function expressionNoComma(type) {
  function expressionInner (line 362) | function expressionInner(type, noComma) {
  function maybeexpression (line 380) | function maybeexpression(type) {
  function maybeexpressionNoComma (line 384) | function maybeexpressionNoComma(type) {
  function maybeoperatorComma (line 389) | function maybeoperatorComma(type, value) {
  function maybeoperatorNoComma (line 393) | function maybeoperatorNoComma(type, value, noComma) {
  function quasi (line 408) | function quasi(type, value) {
  function continueQuasi (line 413) | function continueQuasi(type) {
  function arrowBody (line 420) | function arrowBody(type) {
  function arrowBodyNoComma (line 424) | function arrowBodyNoComma(type) {
  function maybelabel (line 428) | function maybelabel(type) {
  function property (line 432) | function property(type) {
  function objprop (line 435) | function objprop(type, value) {
  function getterSetter (line 449) | function getterSetter(type) {
  function afterprop (line 454) | function afterprop(type) {
  function commasep (line 458) | function commasep(what, end) {
  function contCommasep (line 473) | function contCommasep(what, end, info) {
  function block (line 478) | function block(type) {
  function maybetype (line 482) | function maybetype(type) {
  function typedef (line 485) | function typedef(type) {
  function vardef (line 488) | function vardef() {
  function pattern (line 491) | function pattern(type, value) {
  function proppattern (line 496) | function proppattern(type, value) {
  function maybeAssign (line 504) | function maybeAssign(_type, value) {
  function vardefCont (line 507) | function vardefCont(type) {
  function maybeelse (line 510) | function maybeelse(type, value) {
  function forspec (line 513) | function forspec(type) {
  function forspec1 (line 516) | function forspec1(type) {
  function formaybeinof (line 522) | function formaybeinof(_type, value) {
  function forspec2 (line 526) | function forspec2(type, value) {
  function forspec3 (line 531) | function forspec3(type) {
  function functiondef (line 534) | function functiondef(type, value) {
  function funarg (line 539) | function funarg(type) {
  function className (line 543) | function className(type, value) {
  function classNameAfter (line 546) | function classNameAfter(type, value) {
  function classBody (line 550) | function classBody(type, value) {
  function classGetterSetter (line 563) | function classGetterSetter(type) {
  function afterModule (line 568) | function afterModule(type, value) {
  function afterExport (line 572) | function afterExport(_type, value) {
  function afterImport (line 577) | function afterImport(type) {
  function importSpec (line 581) | function importSpec(type, value) {
  function maybeFrom (line 586) | function maybeFrom(_type, value) {
  function arrayLiteral (line 589) | function arrayLiteral(type) {
  function maybeArrayComprehension (line 593) | function maybeArrayComprehension(type) {
  function comprehension (line 598) | function comprehension(type) {
  function isContinuedStatement (line 603) | function isContinuedStatement(state, textAfter) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/javascript/test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...
  function LD (line 167) | function LD(name) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/jinja2/jinja2.js
  function tokenBase (line 36) | function tokenBase (stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/julia/julia.js
  function wordRegexp (line 17) | function wordRegexp(words) {
  function in_array (line 39) | function in_array(state) {
  function cur_scope (line 49) | function cur_scope(state) {
  function tokenBase (line 57) | function tokenBase(stream, state) {
  function tokenStringFactory (line 214) | function tokenStringFactory(delimiter) {
  function tokenLexer (line 249) | function tokenLexer(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/kotlin/kotlin.js
  function words (line 15) | function words(str) {
  function tokenBase (line 34) | function tokenBase(stream, state) {
  function startString (line 96) | function startString(quote, stream, state) {
  function tokenBaseUntilBrace (line 137) | function tokenBaseUntilBrace() {
  function tokenBaseUntilSpace (line 157) | function tokenBaseUntilSpace() {
  function tokenComment (line 174) | function tokenComment(stream, state) {
  function expectExpression (line 186) | function expectExpression(last) {
  function Context (line 191) | function Context(indented, column, type, align, prev) {
  function pushContext (line 199) | function pushContext(state, col, type) {
  function popContext (line 203) | function popContext(state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/lua/lua.js
  function prefixRE (line 21) | function prefixRE(words) {
  function wordRE (line 24) | function wordRE(words) {
  function readBracket (line 70) | function readBracket(stream) {
  function normal (line 77) | function normal(stream, state) {
  function bracketed (line 100) | function bracketed(level, style) {
  function string (line 113) | function string(quote) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/markdown/markdown.js
  function getMode (line 19) | function getMode(name) {
  function switchInline (line 79) | function switchInline(stream, state, f) {
  function switchBlock (line 84) | function switchBlock(stream, state, f) {
  function blankLine (line 92) | function blankLine(state) {
  function blockNormal (line 115) | function blockNormal(stream, state) {
  function htmlBlock (line 190) | function htmlBlock(stream, state) {
  function local (line 201) | function local(stream, state) {
  function leavingLocal (line 214) | function leavingLocal(stream, state) {
  function getType (line 226) | function getType(state) {
  function handleText (line 308) | function handleText(stream, state) {
  function inlineNormal (line 315) | function inlineNormal(stream, state) {
  function linkInline (line 532) | function linkInline(stream, state) {
  function linkHref (line 552) | function linkHref(stream, state) {
  function getLinkHrefInside (line 567) | function getLinkHrefInside(endChar) {
  function footnoteLink (line 588) | function footnoteLink(stream, state) {
  function footnoteLinkInside (line 599) | function footnoteLinkInside(stream, state) {
  function footnoteUrl (line 613) | function footnoteUrl(stream, state) {
  function inlineRE (line 631) | function inlineRE(endChar) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/markdown/test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...
  function FT (line 8) | function FT(name) { test.mode(name, modeHighlightFormatting, Array.proto...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/mirc/mirc.js
  function parseWords (line 18) | function parseWords(str) {
  function chain (line 86) | function chain(stream, state, f) {
  function tokenBase (line 90) | function tokenBase(stream, state) {
  function tokenComment (line 153) | function tokenComment(stream, state) {
  function tokenUnparsed (line 164) | function tokenUnparsed(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/mllike/mllike.js
  function tokenBase (line 50) | function tokenBase(stream, state) {
  function tokenString (line 91) | function tokenString(stream, state) {
  function tokenComment (line 106) | function tokenComment(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/modelica/modelica.js
  function tokenLineComment (line 30) | function tokenLineComment(stream, state) {
  function tokenBlockComment (line 36) | function tokenBlockComment(stream, state) {
  function tokenString (line 48) | function tokenString(stream, state) {
  function tokenIdent (line 62) | function tokenIdent(stream, state) {
  function tokenQIdent (line 81) | function tokenQIdent(stream, state) {
  function tokenUnsignedNuber (line 93) | function tokenUnsignedNuber(stream, state) {
  function words (line 202) | function words(str) {
  function def (line 213) | function def(mimes, mode) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/nginx/nginx.js
  function words (line 16) | function words(str) {
  function ret (line 36) | function ret(style, tp) {type = tp; return style;}
  function tokenBase (line 38) | function tokenBase(stream, state) {
  function tokenCComment (line 97) | function tokenCComment(stream, state) {
  function tokenSGMLComment (line 109) | function tokenSGMLComment(stream, state) {
  function tokenString (line 121) | function tokenString(quote) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/ntriples/ntriples.js
  function transitState (line 59) | function transitState(currState, c) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/octave/octave.js
  function wordRegexp (line 15) | function wordRegexp(words) {
  function tokenTranspose (line 46) | function tokenTranspose(stream, state) {
  function tokenComment (line 57) | function tokenComment(stream, state) {
  function tokenBase (line 66) | function tokenBase(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/pascal/pascal.js
  function words (line 15) | function words(str) {
  function tokenBase (line 27) | function tokenBase(stream, state) {
  function tokenString (line 65) | function tokenString(quote) {
  function tokenComment (line 77) | function tokenComment(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/pegjs/pegjs.js
  function identifier (line 17) | function identifier(stream) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/perl/perl.js
  function tokenChain (line 483) | function tokenChain(stream,state,chain,style,tail){     // NOTE: chain.l...
  function tokenSOMETHING (line 503) | function tokenSOMETHING(stream,state,string){
  function tokenPerl (line 511) | function tokenPerl(stream,state){
  function look (line 804) | function look(stream, c){
  function prefix (line 809) | function prefix(stream, c){
  function suffix (line 819) | function suffix(stream, c){
  function eatSuffix (line 826) | function eatSuffix(stream, c){

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/php/php.js
  function keywords (line 14) | function keywords(str) {
  function matchSequence (line 21) | function matchSequence(list, end) {
  function stringWithEscapes (line 33) | function stringWithEscapes(closing) {
  function stringWithEscapes_ (line 36) | function stringWithEscapes_(stream, state, closing) {
  function dispatch (line 152) | function dispatch(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/php/test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...
  function build_recursive_monsters (line 78) | function build_recursive_monsters(nt, t, n){
  function build_recursive_monsters_2 (line 124) | function build_recursive_monsters_2(mf1, mf2, nt, t, n){

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/pig/pig.js
  function chain (line 28) | function chain(stream, state, f) {
  function ret (line 34) | function ret(tp, style) {
  function tokenComment (line 39) | function tokenComment(stream, state) {
  function tokenString (line 52) | function tokenString(quote) {
  function tokenBase (line 67) | function tokenBase(stream, state) {
  function keywords (line 150) | function keywords(str) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/puppet/puppet.js
  function define (line 22) | function define(style, string) {
  function tokenString (line 47) | function tokenString(stream, state) {
  function tokenize (line 68) | function tokenize(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/python/python.js
  function wordRegexp (line 14) | function wordRegexp(words) {
  function top (line 44) | function top(state) {
  function tokenBase (line 87) | function tokenBase(stream, state) {
  function tokenBaseInner (line 108) | function tokenBaseInner(stream, state) {
  function tokenStringFactory (line 193) | function tokenStringFactory(delimiter) {
  function pushScope (line 226) | function pushScope(stream, state, type) {
  function dedent (line 238) | function dedent(stream, state) {
  function tokenLexer (line 247) | function tokenLexer(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/q/q.js
  function buildRE (line 19) | function buildRE(w){return new RegExp("^("+w.join("|")+")$");}
  function tokenBase (line 20) | function tokenBase(stream,state){
  function tokenLineComment (line 62) | function tokenLineComment(stream,state){
  function tokenBlockComment (line 65) | function tokenBlockComment(stream,state){
  function tokenCommentToEOF (line 72) | function tokenCommentToEOF(stream){return stream.skipToEnd(),"comment";}
  function tokenString (line 73) | function tokenString(stream,state){
  function pushContext (line 82) | function pushContext(state,type,col){state.context={prev:state.context,i...
  function popContext (line 83) | function popContext(state){state.indent=state.context.indent;state.conte...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/r/r.js
  function wordObj (line 15) | function wordObj(str) {
  function tokenBase (line 27) | function tokenBase(stream, state) {
  function tokenString (line 81) | function tokenString(quote) {
  function push (line 102) | function push(state, type, stream) {
  function pop (line 109) | function pop(state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/rst/rst.js
  function format (line 76) | function format(string) {
  function to_normal (line 156) | function to_normal(stream, state) {
  function to_explicit (line 356) | function to_explicit(stream, state) {
  function to_comment (line 463) | function to_comment(stream, state) {
  function to_verbatim (line 467) | function to_verbatim(stream, state) {
  function as_block (line 471) | function as_block(stream, state, token) {
  function to_mode (line 484) | function to_mode(stream, state) {
  function context (line 503) | function context(phase, stage, mode, local) {
  function change (line 507) | function change(state, tok, ctx) {
  function stage (line 512) | function stage(state) {
  function phase (line 516) | function phase(state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/ruby/ruby.js
  function wordObj (line 15) | function wordObj(words) {
  function chain (line 34) | function chain(newtok, stream, state) {
  function tokenBase (line 39) | function tokenBase(stream, state) {
  function tokenBaseUntilBrace (line 152) | function tokenBaseUntilBrace(depth) {
  function tokenBaseOnce (line 168) | function tokenBaseOnce() {
  function readQuoted (line 179) | function readQuoted(quote, style, embed, unescaped) {
  function readHereDoc (line 210) | function readHereDoc(phrase) {
  function readBlockComment (line 217) | function readBlockComment(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/ruby/test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/rust/rust.js
  function r (line 40) | function r(tc, style) {
  function tokenBase (line 45) | function tokenBase(stream, state) {
  function tokenString (line 104) | function tokenString(stream, state) {
  function tokenComment (line 118) | function tokenComment(depth) {
  function pass (line 144) | function pass() {
  function cont (line 147) | function cont() {
  function pushlex (line 152) | function pushlex(type, info) {
  function poplex (line 161) | function poplex() {
  function typecx (line 169) | function typecx() { cx.state.keywords = typeKeywords; }
  function valcx (line 170) | function valcx() { cx.state.keywords = valKeywords; }
  function commasep (line 173) | function commasep(comb, end) {
  function stat_of (line 185) | function stat_of(comb, tag) {
  function block (line 188) | function block(type) {
  function endstatement (line 201) | function endstatement(type) {
  function expression (line 205) | function expression(type) {
  function maybeop (line 218) | function maybeop(type) {
  function maybeprop (line 225) | function maybeprop() {
  function exprbrace (line 229) | function exprbrace(type) {
  function record_of (line 239) | function record_of(comb) {
  function blockvars (line 249) | function blockvars(type) {
  function letdef1 (line 255) | function letdef1(type) {
  function letdef2 (line 261) | function letdef2(type) {
  function maybetype (line 265) | function maybetype(type) {
  function inop (line 269) | function inop(type) {
  function fndef (line 273) | function fndef(type) {
  function tydef (line 283) | function tydef(type) {
  function enumdef (line 289) | function enumdef(type) {
  function enumblock (line 296) | function enumblock(type) {
  function mod (line 302) | function mod(type) {
  function iface (line 307) | function iface(type) {
  function impl (line 313) | function impl(type) {
  function typarams (line 320) | function typarams() {
  function argdef (line 326) | function argdef(type) {
  function rtype (line 331) | function rtype(type) {
  function rtypemaybeparam (line 340) | function rtypemaybeparam() {
  function fntype (line 344) | function fntype(type) {
  function pattern (line 349) | function pattern(type) {
  function patternmaybeop (line 356) | function patternmaybeop(type) {
  function altbody (line 361) | function altbody(type) {
  function altblock1 (line 365) | function altblock1(type) {
  function altblock2 (line 372) | function altblock2(type) {
  function macro (line 377) | function macro(type) {
  function matchBrackets (line 381) | function matchBrackets(type, comb) {
  function parse (line 388) | function parse(state, stream, style) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/sass/sass.js
  function tokenRegexp (line 15) | function tokenRegexp(words) {
  function urlTokens (line 28) | function urlTokens(stream, state) {
  function comment (line 48) | function comment(indentation, multiLine) {
  function buildStringTokenizer (line 67) | function buildStringTokenizer(quote, greedy) {
  function buildInterpolationTokenizer (line 93) | function buildInterpolationTokenizer(currentTokenizer) {
  function indent (line 105) | function indent(state) {
  function dedent (line 114) | function dedent(state) {
  function tokenBase (line 120) | function tokenBase(stream, state) {
  function tokenLexer (line 356) | function tokenLexer(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/scheme/scheme.js
  function makeKeywords (line 23) | function makeKeywords(str) {
  function stateStack (line 32) | function stateStack(indent, type, prev) { // represents a state stack ob...
  function pushStack (line 38) | function pushStack(state, indent, type) {
  function popStack (line 42) | function popStack(state) {
  function isBinaryNumber (line 51) | function isBinaryNumber (stream) {
  function isOctalNumber (line 55) | function isOctalNumber (stream) {
  function isDecimalNumber (line 59) | function isDecimalNumber (stream, backup) {
  function isHexNumber (line 66) | function isHexNumber (stream) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/shell/shell.js
  function define (line 17) | function define(style, string) {
  function tokenBase (line 38) | function tokenBase(stream, state) {
  function tokenString (line 84) | function tokenString(quote) {
  function tokenize (line 123) | function tokenize(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/shell/test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/sieve/sieve.js
  function words (line 15) | function words(str) {
  function tokenBase (line 25) | function tokenBase(stream, state) {
  function tokenMultiLineString (line 112) | function tokenMultiLineString(stream, state)
  function tokenCComment (line 137) | function tokenCComment(stream, state) {
  function tokenString (line 149) | function tokenString(quote) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/slim/slim.js
  function backup (line 72) | function backup(pos, tokenize, style) {
  function maybeBackup (line 87) | function maybeBackup(stream, state, pat, offset, style) {
  function continueLine (line 97) | function continueLine(state, column) {
  function finishContinue (line 106) | function finishContinue(state) {
  function lineContinuable (line 113) | function lineContinuable(column, tokenize) {
  function commaContinuable (line 127) | function commaContinuable(column, tokenize) {
  function rubyInQuote (line 138) | function rubyInQuote(endQuote, tokenize) {
  function startRubySplat (line 152) | function startRubySplat(tokenize) {
  function ruby (line 174) | function ruby(stream, state) {
  function htmlLine (line 178) | function htmlLine(stream, state) {
  function html (line 184) | function html(stream, state) {
  function startHtmlLine (line 192) | function startHtmlLine(lastTokenize) {
  function startHtmlMode (line 200) | function startHtmlMode(stream, state, offset) {
  function comment (line 211) | function comment(stream, state) {
  function commentMode (line 216) | function commentMode(stream, state) {
  function attributeWrapper (line 227) | function attributeWrapper(stream, state) {
  function attributeWrapperAssign (line 241) | function attributeWrapperAssign(stream, state) {
  function attributeWrapperValue (line 248) | function attributeWrapperValue(stream, state) {
  function startAttributeWrapperMode (line 265) | function startAttributeWrapperMode(state, endQuote, tokenize) {
  function sub (line 278) | function sub(stream, state) {
  function firstSub (line 292) | function firstSub(stream, state) {
  function createMode (line 298) | function createMode(mode) {
  function getMode (line 311) | function getMode(mode) {
  function startSubMode (line 318) | function startSubMode(mode, state) {
  function doctypeLine (line 335) | function doctypeLine(stream, _state) {
  function startLine (line 340) | function startLine(stream, state) {
  function slim (line 368) | function slim(stream, state) {
  function slimTag (line 375) | function slimTag(stream, state) {
  function slimTagExtras (line 386) | function slimTagExtras(stream, state) {
  function slimClass (line 393) | function slimClass(stream, state) {
  function slimAttribute (line 404) | function slimAttribute(stream, state) {
  function slimAttributeAssign (line 419) | function slimAttributeAssign(stream, state) {
  function slimAttributeValue (line 428) | function slimAttributeValue(stream, state) {
  function slimAttributeSymbols (line 447) | function slimAttributeSymbols(stream, state) {
  function readQuoted (line 456) | function readQuoted(quote, style, embed, unescaped, nextTokenize) {
  function slimContent (line 490) | function slimContent(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/slim/test.js
  function MT (line 8) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/smartymixed/smartymixed.js
  function reEsc (line 41) | function reEsc(str) { return str.replace(/[^\s\w]/g, "\\$&"); }

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/solr/solr.js
  function isNumber (line 21) | function isNumber(word) {
  function tokenString (line 25) | function tokenString(quote) {
  function tokenOperator (line 38) | function tokenOperator(operator) {
  function tokenWord (line 57) | function tokenWord(ch) {
  function tokenBase (line 76) | function tokenBase(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/soy/soy.js
  function last (line 29) | function last(array) {
  function tokenUntil (line 33) | function tokenUntil(stream, state, untilRegExp) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/sparql/sparql.js
  function wordRegexp (line 18) | function wordRegexp(words) {
  function tokenBase (line 37) | function tokenBase(stream, state) {
  function tokenLiteral (line 91) | function tokenLiteral(quote) {
  function pushContext (line 105) | function pushContext(state, type, col) {
  function popContext (line 108) | function popContext(state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/sql/sql.js
  function tokenBase (line 26) | function tokenBase(stream, state) {
  function tokenLiteral (line 120) | function tokenLiteral(quote) {
  function tokenComment (line 133) | function tokenComment(stream, state) {
  function pushContext (line 149) | function pushContext(stream, state, type) {
  function popContext (line 158) | function popContext(state) {
  function hookIdentifier (line 209) | function hookIdentifier(stream) {
  function hookVar (line 221) | function hookVar(stream) {
  function hookClient (line 248) | function hookClient(stream) {
  function set (line 263) | function set(str) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/stex/stex.js
  function pushCommand (line 22) | function pushCommand(state, command) {
  function peekCommand (line 26) | function peekCommand(state) {
  function popCommand (line 34) | function popCommand(state) {
  function getMostPowerful (line 42) | function getMostPowerful(state) {
  function addPluginPattern (line 54) | function addPluginPattern(pluginName, cmdStyle, styles) {
  function setState (line 88) | function setState(state, f) {
  function normal (line 93) | function normal(source, state) {
  function inMathMode (line 160) | function inMathMode(source, state, endModeSeq) {
  function beginParams (line 205) | function beginParams(source, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/stex/test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/stylus/stylus.js
  function urlTokens (line 312) | function urlTokens(stream, state) {
  function multilineComment (line 333) | function multilineComment(stream, state) {
  function buildStringTokenizer (line 344) | function buildStringTokenizer(quote, greedy) {
  function buildInterpolationTokenizer (line 376) | function buildInterpolationTokenizer(currentTokenizer) {
  function indent (line 388) | function indent(state) {
  function dedent (line 397) | function dedent(state) {
  function wordRegexp (line 429) | function wordRegexp(words) {
  function keySet (line 433) | function keySet(array) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/tcl/tcl.js
  function parseWords (line 17) | function parseWords(str) {
  function chain (line 37) | function chain(stream, state, f) {
  function tokenBase (line 41) | function tokenBase(stream, state) {
  function tokenString (line 92) | function tokenString(quote) {
  function tokenComment (line 106) | function tokenComment(stream, state) {
  function tokenUnparsed (line 117) | function tokenUnparsed(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/textile/test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/textile/textile.js
  function startNewLine (line 49) | function startNewLine(stream, state) {
  function handlePhraseModifier (line 58) | function handlePhraseModifier(stream, state, ch) {
  function togglePhraseModifier (line 119) | function togglePhraseModifier(stream, state, phraseModifier, closeRE, op...
  function tokenStyles (line 136) | function tokenStyles(state) {
  function textileDisabled (line 153) | function textileDisabled(state) {
  function tokenStylesWith (line 168) | function tokenStylesWith(state, extraStyles) {
  function activeStyles (line 179) | function activeStyles(state) {
  function blankLine (line 188) | function blankLine(state) {
  function RE (line 288) | function RE(name) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/tiddlywiki/tiddlywiki.js
  function kw (line 36) | function kw(type) {
  function chain (line 69) | function chain(stream, state, f) {
  function ret (line 78) | function ret(tp, style, cont) {
  function jsTokenBase (line 84) | function jsTokenBase(stream, state) {
  function twTokenComment (line 221) | function twTokenComment(stream, state) {
  function twTokenStrong (line 235) | function twTokenStrong(stream, state) {
  function twTokenCode (line 249) | function twTokenCode(stream, state) {
  function twTokenEm (line 271) | function twTokenEm(stream, state) {
  function twTokenUnderline (line 285) | function twTokenUnderline(stream, state) {
  function twTokenStrike (line 300) | function twTokenStrike(stream, state) {
  function twTokenMacro (line 314) | function twTokenMacro(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/tiki/tiki.js
  function inBlock (line 15) | function inBlock(style, terminator, returnTokenizer) {
  function inLine (line 31) | function inLine(style) {
  function inText (line 41) | function inText(stream, state) {
  function inPlugin (line 144) | function inPlugin(stream, state) {
  function inAttribute (line 178) | function inAttribute(quote) {
  function inAttributeNoQuote (line 190) | function inAttributeNoQuote() {
  function pass (line 205) | function pass() {
  function cont (line 209) | function cont() {
  function pushContext (line 214) | function pushContext(pluginName, startOfLine) {
  function popContext (line 225) | function popContext() {
  function element (line 229) | function element(type) {
  function endplugin (line 250) | function endplugin(startOfLine) {
  function endcloseplugin (line 262) | function endcloseplugin(err) {
  function attributes (line 270) | function attributes(type) {
  function attvalue (line 275) | function attvalue(type) {
  function attvaluemaybe (line 280) | function attvaluemaybe(type) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/tornado/tornado.js
  function tokenBase (line 25) | function tokenBase (stream, state) {
  function inTag (line 35) | function inTag (close) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/turtle/turtle.js
  function wordRegexp (line 18) | function wordRegexp(words) {
  function tokenBase (line 25) | function tokenBase(stream, state) {
  function tokenLiteral (line 77) | function tokenLiteral(quote) {
  function pushContext (line 91) | function pushContext(state, type, col) {
  function popContext (line 94) | function popContext(state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/vb/vb.js
  function wordRegexp (line 17) | function wordRegexp(words) {
  function indent (line 53) | function indent(_stream, state) {
  function dedent (line 57) | function dedent(_stream, state) {
  function tokenBase (line 61) | function tokenBase(stream, state) {
  function tokenStringFactory (line 171) | function tokenStringFactory(delimiter) {
  function tokenLexer (line 197) | function tokenLexer(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/vbscript/vbscript.js
  function wordRegexp (line 28) | function wordRegexp(words) {
  function indent (line 108) | function indent(_stream, state) {
  function dedent (line 112) | function dedent(_stream, state) {
  function tokenBase (line 116) | function tokenBase(stream, state) {
  function tokenStringFactory (line 259) | function tokenStringFactory(delimiter) {
  function tokenLexer (line 285) | function tokenLexer(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/velocity/velocity.js
  function parseWords (line 15) | function parseWords(str) {
  function chain (line 28) | function chain(stream, state, f) {
  function tokenBase (line 32) | function tokenBase(stream, state) {
  function tokenString (line 129) | function tokenString(quote) {
  function tokenComment (line 149) | function tokenComment(stream, state) {
  function tokenUnparsed (line 161) | function tokenUnparsed(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/verilog/test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/verilog/verilog.js
  function words (line 23) | function words(str) {
  function tokenBase (line 110) | function tokenBase(stream, state) {
  function tokenString (line 204) | function tokenString(quote) {
  function tokenComment (line 217) | function tokenComment(stream, state) {
  function Context (line 229) | function Context(indented, column, type, align, prev) {
  function pushContext (line 236) | function pushContext(state, col, type) {
  function popContext (line 241) | function popContext(state) {
  function isClosing (line 249) | function isClosing(text, contextClosing) {
  function buildElectricInputRegEx (line 264) | function buildElectricInputRegEx() {
  function svxGenIndent (line 385) | function svxGenIndent(stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/xml/test.js
  function MT (line 6) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/xml/xml.js
  function inText (line 66) | function inText(stream, state) {
  function inTag (line 113) | function inTag(stream, state) {
  function inAttribute (line 138) | function inAttribute(quote) {
  function inBlock (line 152) | function inBlock(style, terminator) {
  function doctype (line 164) | function doctype(depth) {
  function Context (line 185) | function Context(state, tagName, startOfLine) {
  function popContext (line 193) | function popContext(state) {
  function maybePopContext (line 196) | function maybePopContext(state, nextTagName) {
  function baseState (line 211) | function baseState(type, stream, state) {
  function tagNameState (line 221) | function tagNameState(type, stream, state) {
  function closeTagNameState (line 231) | function closeTagNameState(type, stream, state) {
  function closeState (line 250) | function closeState(type, _stream, state) {
  function closeStateErr (line 258) | function closeStateErr(type, stream, state) {
  function attrState (line 263) | function attrState(type, _stream, state) {
  function attrEqState (line 282) | function attrEqState(type, stream, state) {
  function attrValueState (line 287) | function attrValueState(type, stream, state) {
  function attrContinuedState (line 293) | function attrContinuedState(type, stream, state) {

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/xquery/test.js
  function MT (line 11) | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arg...

FILE: src/main/resources/static/lib/editormd/lib/codemirror/mode/xquery/xquery.js
  function kw (line 21) | function kw(type) {return {type: type, style: "keyword"};}
  function ret (line 75) | function ret(tp, style, cont) {
  function chain (line 80) | function chain(stream, state, f) {
  function tokenBase (line 86) | function tokenBase(stream, state) {
  function tokenComment (line 220) | function tokenComment(stream, state) {
  function tokenString (line 243) | function tokenString(quote, f) {
  function tokenVariable (line 283) | function tokenVariable(stream, state) {
  function tokenTag (line 300) | function tokenTag(name, isclose) {
  function tokenAttribute (line 323) | function tokenAttribute(stream, state) {
  function tokenXMLComment (line 358) | function tokenXMLComment(stream, state) {
  function tokenCDATA (line 370) | function tokenCDATA(stream, state) {
  function tokenPreProcessing (line 381) | function tokenPreProcessing(stream, state) {
  function isInXmlBlock (line 393) | function isInXmlBlock(state) { return isIn(state, "tag"); }
  function isInXmlAttributeBlock (line 394) | function isInXmlAttributeBlock(state) { return isIn(state, "attribute"); }
  function isInXmlConstructor (line 395) | function isInXmlConstructor(state) { return isIn(state, "xmlconstructor"...
  function isInString (line 396) | function isInString(state) { return isIn(state, "string"); }
  function isEQNameAhead (line 398) | function isEQNameAhead(stream) {
  function isIn (line 408) | function isIn(state, type) {
  function pushStateStack (line 412) | function pushStateStack(state, newState) {
  function popStateStack (line 416) | function popStateStack(state) {

FILE: src/main/resources/static/lib/tocbot/tocbot.js
  function __webpack_require__ (line 6) | function __webpack_require__(moduleId) {

FILE: src/test/java/com/blog/BlogApplicationTests.java
  class BlogApplicationTests (line 12) | @SpringBootTest
    method contextLoads (line 15) | @Test

FILE: src/test/java/com/blog/service/AIServiceTest.java
  class AIServiceTest (line 15) | @SpringBootTest
    method setUp (line 25) | @BeforeEach
    method testGenerateSummary_WithLongContent (line 30) | @Test
    method testGenerateSummary_WithShortContent (line 46) | @Test
    method testSuggestTags (line 59) | @Test
    method testScoreArticle (line 73) | @Test
    method testIsEnabled (line 88) | @Test
    method testLocalSummaryGeneration (line 95) | @Test

FILE: src/test/java/com/blog/service/SitemapServiceTest.java
  class SitemapServiceTest (line 22) | class SitemapServiceTest {
    method setUp (line 30) | @BeforeEach
    method testGenerateSitemap (line 35) | @Test
    method testGenerateSitemap_WithEmptyBlogs (line 72) | @Test
    method testGenerateSitemap_ContainsPriority (line 88) | @Test
    method testGenerateSitemap_ContainsChangeFreq (line 104) | @Test

FILE: src/test/java/com/blog/util/PasswordUtilsTest.java
  class PasswordUtilsTest (line 11) | class PasswordUtilsTest {
    method testEncode (line 13) | @Test
    method testMatches_WithCorrectPassword (line 27) | @Test
    method testMatches_WithWrongPassword (line 40) | @Test
    method testEncode_DifferentResultsForSamePassword (line 54) | @Test
    method testMatches_WithEmptyPassword (line 69) | @Test
    method testMatches_WithLongPassword (line 82) | @Test

FILE: src/test/java/com/blog/util/SEOUtilsTest.java
  class SEOUtilsTest (line 17) | class SEOUtilsTest {
    method setUp (line 22) | @BeforeEach
    method testGenerateMetaDescription_WithDescription (line 46) | @Test
    method testGenerateMetaDescription_WithoutDescription (line 57) | @Test
    method testGenerateMetaKeywords (line 70) | @Test
    method testGenerateOpenGraphTags (line 82) | @Test
    method testGenerateTwitterCardTags (line 97) | @Test
    method testGenerateJsonLd (line 110) | @Test
    method testGenerateBreadcrumbJsonLd (line 124) | @Test
    method testGenerateFaqJsonLd (line 141) | @Test
    method testHtmlEscape (line 159) | @Test
Condensed preview — 527 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,201K chars).
[
  {
    "path": ".gitattributes",
    "chars": 87,
    "preview": "*.js linguist-language=Java\n*.css linguist-language=Java\n*.html linguist-language=Java\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 595,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 507,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".gitignore",
    "chars": 228,
    "preview": "/target/\n/.idea/\n/blog-dev.log\n/log/\n\n# Node.js dependencies in static resources (security risk)\nsrc/main/resources/stat"
  },
  {
    "path": "Dockerfile",
    "chars": 1121,
    "preview": "# SpringBoot AI Blog - Docker 镜像\nFROM openjdk:11-jre-slim\n\n# 作者信息\nLABEL maintainer=\"tangredtea <tangredtea@gmail.com>\"\nL"
  },
  {
    "path": "LICENSE.txt",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 7159,
    "preview": "# SpringBoot AI Blog\n\n[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)\n[![Stars](https:"
  },
  {
    "path": "README_CN.md",
    "chars": 4948,
    "preview": "# SpringBoot AI 博客系统\n\n[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)\n[![Stars](https:"
  },
  {
    "path": "blog.sql",
    "chars": 22689,
    "preview": "-- --------------------------------------------------------\n-- Spring-Blog 初始化数据\n-- 适用于 MySQL 8.x\n-- -------------------"
  },
  {
    "path": "docker-compose.yml",
    "chars": 1674,
    "preview": "version: '3.8'\n\nservices:\n  # MySQL 数据库\n  mysql:\n    image: mysql:8.0\n    container_name: blog-mysql\n    restart: always"
  },
  {
    "path": "nginx.conf",
    "chars": 541,
    "preview": "http {\n include  mime.types;\n default_type application/octet-stream;\n sendfile  on;\n keepalive_timeout 65;\n gzip on;\n\n s"
  },
  {
    "path": "pom.xml",
    "chars": 5027,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
  },
  {
    "path": "src/main/java/com/blog/BlogApplication.java",
    "chars": 547,
    "preview": "package com.blog;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.Spri"
  },
  {
    "path": "src/main/java/com/blog/aspect/LogAspect.java",
    "chars": 1876,
    "preview": "package com.blog.aspect;\n\nimport com.blog.pojo.RequestLog;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.an"
  },
  {
    "path": "src/main/java/com/blog/config/RedisConfig.java",
    "chars": 2476,
    "preview": "package com.blog.config;\n\nimport com.fasterxml.jackson.annotation.JsonAutoDetect;\nimport com.fasterxml.jackson.annotatio"
  },
  {
    "path": "src/main/java/com/blog/config/RedisKey.java",
    "chars": 435,
    "preview": "package com.blog.config;\n\n/**\n * @author tangredtea\n */\npublic final class RedisKey {\n    public static final String ART"
  },
  {
    "path": "src/main/java/com/blog/config/SettingsConfig.java",
    "chars": 1993,
    "preview": "package com.blog.config;\n\nimport lombok.*;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springf"
  },
  {
    "path": "src/main/java/com/blog/config/WebMvcConfig.java",
    "chars": 849,
    "preview": "package com.blog.config;\n\nimport com.blog.interceptor.LoginInterceptor;\nimport org.springframework.context.annotation.Co"
  },
  {
    "path": "src/main/java/com/blog/controller/SitemapController.java",
    "chars": 739,
    "preview": "package com.blog.controller;\n\nimport com.blog.service.SitemapService;\nimport org.springframework.http.MediaType;\nimport "
  },
  {
    "path": "src/main/java/com/blog/controller/admin/AIController.java",
    "chars": 2941,
    "preview": "package com.blog.controller.admin;\n\nimport com.blog.service.AIService;\nimport com.blog.util.CommonResult;\nimport lombok."
  },
  {
    "path": "src/main/java/com/blog/controller/admin/AdminController.java",
    "chars": 5387,
    "preview": "package com.blog.controller.admin;\n\nimport com.blog.entity.User;\nimport com.blog.service.*;\nimport com.blog.util.Passwor"
  },
  {
    "path": "src/main/java/com/blog/controller/admin/BlogController.java",
    "chars": 4951,
    "preview": "package com.blog.controller.admin;\n\nimport com.blog.config.RedisKey;\nimport com.blog.entity.Blog;\nimport com.blog.entity"
  },
  {
    "path": "src/main/java/com/blog/controller/admin/FriendLinkController.java",
    "chars": 3713,
    "preview": "package com.blog.controller.admin;\n\nimport com.blog.entity.FriendLink;\nimport com.blog.service.FriendLinkService;\nimport"
  },
  {
    "path": "src/main/java/com/blog/controller/admin/SettingsController.java",
    "chars": 1228,
    "preview": "package com.blog.controller.admin;\n\nimport com.blog.config.SettingsConfig;\nimport com.blog.util.PropertiesUtil;\nimport o"
  },
  {
    "path": "src/main/java/com/blog/controller/admin/TagController.java",
    "chars": 2539,
    "preview": "package com.blog.controller.admin;\n\nimport com.blog.entity.Tag;\nimport com.blog.service.TagService;\nimport com.github.pa"
  },
  {
    "path": "src/main/java/com/blog/controller/admin/TypeController.java",
    "chars": 2595,
    "preview": "package com.blog.controller.admin;\n\nimport com.blog.entity.Type;\nimport com.blog.service.TypeService;\nimport com.github."
  },
  {
    "path": "src/main/java/com/blog/controller/blog/AboutShowController.java",
    "chars": 308,
    "preview": "package com.blog.controller.blog;\n\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind"
  },
  {
    "path": "src/main/java/com/blog/controller/blog/ArchiveShowController.java",
    "chars": 619,
    "preview": "package com.blog.controller.blog;\n\nimport com.blog.service.BlogService;\nimport org.springframework.stereotype.Controller"
  },
  {
    "path": "src/main/java/com/blog/controller/blog/FriendLinkControllerShow.java",
    "chars": 588,
    "preview": "package com.blog.controller.blog;\n\nimport com.blog.service.FriendLinkService;\nimport org.springframework.stereotype.Cont"
  },
  {
    "path": "src/main/java/com/blog/controller/blog/IndexController.java",
    "chars": 5901,
    "preview": "package com.blog.controller.blog;\n\nimport com.blog.config.RedisKey;\nimport com.blog.entity.Blog;\nimport com.blog.entity."
  },
  {
    "path": "src/main/java/com/blog/controller/blog/MessageController.java",
    "chars": 2422,
    "preview": "package com.blog.controller.blog;\n\nimport com.blog.config.RedisKey;\nimport com.blog.config.SettingsConfig;\nimport com.bl"
  },
  {
    "path": "src/main/java/com/blog/controller/blog/TagShowController.java",
    "chars": 1549,
    "preview": "package com.blog.controller.blog;\n\nimport com.blog.entity.Blog;\nimport com.blog.entity.Tag;\nimport com.blog.service.Blog"
  },
  {
    "path": "src/main/java/com/blog/controller/blog/TypeShowController.java",
    "chars": 1520,
    "preview": "package com.blog.controller.blog;\n\nimport com.blog.entity.Blog;\nimport com.blog.entity.Type;\nimport com.blog.service.Blo"
  },
  {
    "path": "src/main/java/com/blog/controller/common/ControllerExceptionHandler.java",
    "chars": 2042,
    "preview": "package com.blog.controller.common;\n\nimport com.blog.config.SettingsConfig;\nimport com.blog.pojo.WebhookMessage;\nimport "
  },
  {
    "path": "src/main/java/com/blog/dao/BlogDao.java",
    "chars": 2764,
    "preview": "package com.blog.dao;\n\nimport com.blog.entity.Blog;\nimport com.blog.entity.BlogAndTag;\nimport org.apache.ibatis.annotati"
  },
  {
    "path": "src/main/java/com/blog/dao/FriendLinkDao.java",
    "chars": 1022,
    "preview": "package com.blog.dao;\n\nimport com.blog.entity.FriendLink;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.spring"
  },
  {
    "path": "src/main/java/com/blog/dao/MessageDao.java",
    "chars": 1156,
    "preview": "package com.blog.dao;\n\nimport com.blog.entity.Message;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ib"
  },
  {
    "path": "src/main/java/com/blog/dao/TagDao.java",
    "chars": 971,
    "preview": "package com.blog.dao;\n\nimport com.blog.entity.Tag;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.springframewo"
  },
  {
    "path": "src/main/java/com/blog/dao/TypeDao.java",
    "chars": 1000,
    "preview": "package com.blog.dao;\n\nimport com.blog.entity.Type;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.springframew"
  },
  {
    "path": "src/main/java/com/blog/dao/UserDao.java",
    "chars": 1336,
    "preview": "package com.blog.dao;\n\nimport com.blog.entity.User;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibati"
  },
  {
    "path": "src/main/java/com/blog/entity/Blog.java",
    "chars": 2391,
    "preview": "package com.blog.entity;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport"
  },
  {
    "path": "src/main/java/com/blog/entity/BlogAndTag.java",
    "chars": 348,
    "preview": "package com.blog.entity;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport"
  },
  {
    "path": "src/main/java/com/blog/entity/FriendLink.java",
    "chars": 445,
    "preview": "package com.blog.entity;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport"
  },
  {
    "path": "src/main/java/com/blog/entity/Message.java",
    "chars": 735,
    "preview": "package com.blog.entity;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport"
  },
  {
    "path": "src/main/java/com/blog/entity/Tag.java",
    "chars": 415,
    "preview": "package com.blog.entity;\n\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimpor"
  },
  {
    "path": "src/main/java/com/blog/entity/Type.java",
    "chars": 417,
    "preview": "package com.blog.entity;\n\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimpor"
  },
  {
    "path": "src/main/java/com/blog/entity/User.java",
    "chars": 530,
    "preview": "package com.blog.entity;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport"
  },
  {
    "path": "src/main/java/com/blog/enums/BlogStatus.java",
    "chars": 370,
    "preview": "package com.blog.enums;\n\nimport lombok.Getter;\n\n/**\n * 博客文章状态枚举\n * @author tangredtea\n */\n@Getter\npublic enum BlogStatus"
  },
  {
    "path": "src/main/java/com/blog/exception/BusinessException.java",
    "chars": 450,
    "preview": "package com.blog.exception;\n\nimport lombok.Getter;\n\n/**\n * 业务异常\n * @author tangredtea\n */\n@Getter\npublic class BusinessE"
  },
  {
    "path": "src/main/java/com/blog/exception/GlobalExceptionHandler.java",
    "chars": 996,
    "preview": "package com.blog.exception;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.ui.Model;\nimport org.springfra"
  },
  {
    "path": "src/main/java/com/blog/exception/NotFoundException.java",
    "chars": 522,
    "preview": "package com.blog.exception;\n\nimport org.springframework.http.HttpStatus;\nimport org.springframework.web.bind.annotation."
  },
  {
    "path": "src/main/java/com/blog/interceptor/LoginInterceptor.java",
    "chars": 597,
    "preview": "package com.blog.interceptor;\n\nimport org.springframework.web.servlet.HandlerInterceptor;\n\nimport javax.servlet.http.Htt"
  },
  {
    "path": "src/main/java/com/blog/pojo/RequestLog.java",
    "chars": 282,
    "preview": "package com.blog.pojo;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\n\n/**\n * 日志类,用于封装请求信息\n * @author tangredtea\n"
  },
  {
    "path": "src/main/java/com/blog/pojo/WebhookMessage.java",
    "chars": 641,
    "preview": "package com.blog.pojo;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport ja"
  },
  {
    "path": "src/main/java/com/blog/scheduled/Refresh.java",
    "chars": 2149,
    "preview": "package com.blog.scheduled;\n\nimport com.blog.dao.BlogDao;\nimport com.blog.config.RedisKey;\nimport com.blog.entity.Blog;\n"
  },
  {
    "path": "src/main/java/com/blog/service/AIService.java",
    "chars": 9654,
    "preview": "package com.blog.service;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\n"
  },
  {
    "path": "src/main/java/com/blog/service/BlogService.java",
    "chars": 1806,
    "preview": "package com.blog.service;\n\nimport com.blog.entity.Blog;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @author ta"
  },
  {
    "path": "src/main/java/com/blog/service/FriendLinkService.java",
    "chars": 922,
    "preview": "package com.blog.service;\n\nimport com.blog.entity.FriendLink;\n\nimport java.util.List;\n\n/**\n * @author tangredtea\n */\npub"
  },
  {
    "path": "src/main/java/com/blog/service/MessageService.java",
    "chars": 596,
    "preview": "package com.blog.service;\n\nimport com.blog.entity.Message;\n\nimport java.util.List;\n\n/**\n * @author tangredtea\n */\npublic"
  },
  {
    "path": "src/main/java/com/blog/service/RedisService.java",
    "chars": 7882,
    "preview": "package com.blog.service;\n\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.geo.Distance;\nim"
  },
  {
    "path": "src/main/java/com/blog/service/SitemapService.java",
    "chars": 2486,
    "preview": "package com.blog.service;\n\nimport com.blog.entity.Blog;\nimport com.blog.dao.BlogDao;\nimport lombok.extern.slf4j.Slf4j;\ni"
  },
  {
    "path": "src/main/java/com/blog/service/SmartSearchService.java",
    "chars": 5296,
    "preview": "package com.blog.service;\n\nimport com.blog.dao.BlogDao;\nimport com.blog.entity.Blog;\nimport lombok.extern.slf4j.Slf4j;\ni"
  },
  {
    "path": "src/main/java/com/blog/service/TagService.java",
    "chars": 994,
    "preview": "package com.blog.service;\n\nimport com.blog.entity.Tag;\n\nimport java.util.List;\n\n/**\n * @author tangredtea\n */\npublic int"
  },
  {
    "path": "src/main/java/com/blog/service/TypeService.java",
    "chars": 899,
    "preview": "package com.blog.service;\n\nimport com.blog.entity.Type;\n\nimport java.util.List;\n\n/**\n * @author tangredtea\n */\npublic in"
  },
  {
    "path": "src/main/java/com/blog/service/UserService.java",
    "chars": 1071,
    "preview": "package com.blog.service;\n\nimport com.blog.entity.User;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.Li"
  },
  {
    "path": "src/main/java/com/blog/service/impl/BlogServiceImpl.java",
    "chars": 4240,
    "preview": "package com.blog.service.impl;\n\nimport com.blog.dao.BlogDao;\nimport com.blog.config.RedisKey;\nimport com.blog.exception."
  },
  {
    "path": "src/main/java/com/blog/service/impl/FriendLinkServiceImpl.java",
    "chars": 1290,
    "preview": "package com.blog.service.impl;\n\nimport com.blog.dao.FriendLinkDao;\nimport com.blog.entity.FriendLink;\nimport com.blog.se"
  },
  {
    "path": "src/main/java/com/blog/service/impl/MessageServiceImpl.java",
    "chars": 3182,
    "preview": "package com.blog.service.impl;\n\nimport com.blog.dao.MessageDao;\nimport com.blog.entity.Message;\nimport com.blog.service."
  },
  {
    "path": "src/main/java/com/blog/service/impl/RedisServiceImpl.java",
    "chars": 7965,
    "preview": "package com.blog.service.impl;\nimport com.blog.service.RedisService;\nimport org.springframework.data.domain.Sort;\nimport"
  },
  {
    "path": "src/main/java/com/blog/service/impl/TagServiceImpl.java",
    "chars": 1932,
    "preview": "package com.blog.service.impl;\n\nimport com.blog.dao.TagDao;\nimport com.blog.entity.Tag;\nimport com.blog.service.TagServi"
  },
  {
    "path": "src/main/java/com/blog/service/impl/TypeServiceImpl.java",
    "chars": 1230,
    "preview": "package com.blog.service.impl;\n\nimport com.blog.dao.TypeDao;\nimport com.blog.entity.Type;\nimport com.blog.service.RedisS"
  },
  {
    "path": "src/main/java/com/blog/service/impl/UserServiceImpl.java",
    "chars": 1505,
    "preview": "package com.blog.service.impl;\n\nimport com.blog.dao.UserDao;\nimport com.blog.entity.User;\nimport com.blog.service.UserSe"
  },
  {
    "path": "src/main/java/com/blog/util/CommonResult.java",
    "chars": 556,
    "preview": "package com.blog.util;\n\nimport lombok.Data;\n\n/**\n * 公共逻辑返回类\n * @author tangredtea\n */\n@Data\npublic class CommonResult<T>"
  },
  {
    "path": "src/main/java/com/blog/util/MD5Utils.java",
    "chars": 992,
    "preview": "package com.blog.util;\n\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\n\n/**\n * @auth"
  },
  {
    "path": "src/main/java/com/blog/util/MarkdownUtils.java",
    "chars": 2264,
    "preview": "package com.blog.util;\n\nimport org.commonmark.Extension;\nimport org.commonmark.ext.gfm.tables.TableBlock;\nimport org.com"
  },
  {
    "path": "src/main/java/com/blog/util/PasswordUtils.java",
    "chars": 734,
    "preview": "package com.blog.util;\n\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\n\n/**\n * 密码加密工具类\n * 使用 B"
  },
  {
    "path": "src/main/java/com/blog/util/PropertiesUtil.java",
    "chars": 1744,
    "preview": "package com.blog.util;\n\n\nimport com.blog.config.SettingsConfig;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframe"
  },
  {
    "path": "src/main/java/com/blog/util/RedisUtil.java",
    "chars": 5778,
    "preview": "package com.blog.util;\n\nimport org.springframework.data.redis.core.*;\nimport org.springframework.stereotype.Component;\n\n"
  },
  {
    "path": "src/main/java/com/blog/util/SEOUtils.java",
    "chars": 8397,
    "preview": "package com.blog.util;\n\nimport com.blog.entity.Blog;\nimport org.springframework.stereotype.Component;\n\n/**\n * SEO 工具类\n *"
  },
  {
    "path": "src/main/java/com/blog/util/WxChatbotClient.java",
    "chars": 4471,
    "preview": "package com.blog.util;\n\nimport com.blog.pojo.WebhookMessage;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport "
  },
  {
    "path": "src/main/resources/application-dev.yml",
    "chars": 2158,
    "preview": "#开发环境\n# 敏感配置请使用环境变量:\n# export DB_PASSWORD=your_password\n# export REDIS_PASSWORD=your_redis_password\n\nspring:\n  datasourc"
  },
  {
    "path": "src/main/resources/application-pro.yml",
    "chars": 1418,
    "preview": "#生产环境\nspring:\n  datasource:\n    #hikari连接池,号称最快的连接池\n    type: com.zaxxer.hikari.HikariDataSource\n    url: jdbc:mysql://1"
  },
  {
    "path": "src/main/resources/application.yml",
    "chars": 459,
    "preview": "spring:            #全局配置\n  thymeleaf:\n    cache: true\n  profiles:\n    active: dev\n  devtools:\n    restart:\n      enabled"
  },
  {
    "path": "src/main/resources/mapper/BlogDao.xml",
    "chars": 8797,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"ht"
  },
  {
    "path": "src/main/resources/mapper/FriendLinkDao.xml",
    "chars": 1469,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org"
  },
  {
    "path": "src/main/resources/mapper/MessageDao.xml",
    "chars": 1681,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org"
  },
  {
    "path": "src/main/resources/mapper/TagDao.xml",
    "chars": 1507,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"ht"
  },
  {
    "path": "src/main/resources/mapper/TypeDao.xml",
    "chars": 1561,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"ht"
  },
  {
    "path": "src/main/resources/mapper/UserDao.xml",
    "chars": 1516,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<!DOCTYPE mapper\n        PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n        \"ht"
  },
  {
    "path": "src/main/resources/messages.properties",
    "chars": 1585,
    "preview": "web_Name=\\u6587\\u82E5\\u541B\nweb_IndexName=\\u81F4\\u529B\\u4E8E\\u5206\\u4EAB\\u540E\\u7AEF\\u6280\\u672F\\u548C\\u8BB0\\u5F55\\u7684"
  },
  {
    "path": "src/main/resources/static/backend/css/style.css",
    "chars": 745112,
    "preview": "@import url(\"./../icons/simple-line-icons/css/simple-line-icons.css\");@import url(\"./../icons/font-awesome/css/font-awes"
  },
  {
    "path": "src/main/resources/static/backend/icons/avasta/css/style.css",
    "chars": 6727,
    "preview": "@font-face{font-family:'avasta';src:url('../fonts/avasta.eot');src:url('../fonts/avasta.eot') format('embedded-opentype'"
  },
  {
    "path": "src/main/resources/static/backend/icons/bootstrap-icons/font/bootstrap-icons.css",
    "chars": 56983,
    "preview": "@font-face{font-family:\"bootstrap-icons\";src:url(\"./fonts/bootstrap-icons.woff2?8bd4575acf83c7696dc7a14a966660a3\") forma"
  },
  {
    "path": "src/main/resources/static/backend/icons/flaticon/flaticon.css",
    "chars": 18375,
    "preview": "@font-face{font-family:\"Flaticon\";src:url(\"./Flaticon.eot\");src:url(\"./Flaticon.eot?#iefix\") format(\"embedded-opentype\")"
  },
  {
    "path": "src/main/resources/static/backend/icons/flaticon_1/flaticon_1.css",
    "chars": 4986,
    "preview": "@font-face{font-family:\"Flaticon\";src:url(\"./Flaticon_1.eot\");src:url(\"./Flaticon_1.eot?#iefix\") format(\"embedded-openty"
  },
  {
    "path": "src/main/resources/static/backend/icons/icomoon/icomoon.css",
    "chars": 81487,
    "preview": "@font-face{font-family:'icomoon';src:url('fonts/icomoon.eot');src:url('fonts/icomoon.eot') format('embedded-opentype'),u"
  },
  {
    "path": "src/main/resources/static/backend/icons/simple-line-icons/css/simple-line-icons.css",
    "chars": 10828,
    "preview": "@font-face{font-family:'simple-line-icons';src:url('../fonts/Simple-Line-Icons4c82.eot?-i3a2kk');src:url('../fonts/Simpl"
  },
  {
    "path": "src/main/resources/static/backend/icons/themify-icons/css/themify-icons.css",
    "chars": 14234,
    "preview": "@font-face{font-family:'themify';src:url('../fonts/themify9f24.eot?-fvbane');src:url('../fonts/themifyd41d.eot?#iefix-fv"
  },
  {
    "path": "src/main/resources/static/backend/js/dashboard/coin-details.js",
    "chars": 5000,
    "preview": "\n\n(function($) {\n    /* \"use strict\" */\n\t\n var dzChartlist = function(){\n\t\n\tvar screenWidth = $(window).width();\t\n\t\tvar "
  },
  {
    "path": "src/main/resources/static/backend/js/dashboard/dashboard-1.js",
    "chars": 3218,
    "preview": "\n\n(function($) {\n    /* \"use strict\" */\n\t\n var dzChartlist = function(){\n\t\n\tvar screenWidth = $(window).width();\t\n\tvar c"
  },
  {
    "path": "src/main/resources/static/backend/js/dashboard/market-capital.js",
    "chars": 607,
    "preview": "\n\n(function($) {\n    /* \"use strict\" */\n\t\n var dzChartlist = function(){\n\t\n\tvar screenWidth = $(window).width();\t\n\t\tvar "
  },
  {
    "path": "src/main/resources/static/backend/js/dashboard/my-wallet.js",
    "chars": 3324,
    "preview": "\n\n(function($) {\n    /* \"use strict\" */\n\t\n var dzChartlist = function(){\n\t\n\tvar screenWidth = $(window).width();\t\n\tvar s"
  },
  {
    "path": "src/main/resources/static/backend/js/dashboard/portofolio.js",
    "chars": 4077,
    "preview": "\n\n(function($) {\n    /* \"use strict\" */\n\t\n var dzChartlist = function(){\n\t\n\tvar screenWidth = $(window).width();\t\n\t\t var"
  },
  {
    "path": "src/main/resources/static/backend/js/demo.js",
    "chars": 5136,
    "preview": "\"use strict\"\nvar themeOptionArr = {\n\t\t\ttypography: '',\n\t\t\tversion: '',\n\t\t\tlayout: '',\n\t\t\tprimary: '',\n\t\t\theaderBg: '',\n\t"
  },
  {
    "path": "src/main/resources/static/backend/js/deznav-init.js",
    "chars": 1294,
    "preview": "\n\"use strict\"\n\nvar dezSettingsOptions = {};\n\nfunction getUrlParams(dParam) \n\t{\n\t\tvar dPageURL = window.location.search.s"
  },
  {
    "path": "src/main/resources/static/backend/js/fullcalendar-init.js",
    "chars": 3247,
    "preview": "\"use strict\"\n document.addEventListener('DOMContentLoaded', function() {\n\n\t\t/* initialize the external events\n\t\t--------"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/bs-daterange-picker-init.js",
    "chars": 892,
    "preview": "(function($) {\n    \"use strict\"\n\n    // Daterange picker\n    $('.input-daterange-datepicker').daterangepicker({\n        "
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/chartist-init.js",
    "chars": 21387,
    "preview": "(function($) {\n    \"use strict\" \n\n\n var dzChartlist = function(){\n\t\n\tvar screenWidth = $(window).width();\n\t\t\n\tvar setCha"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/chartjs-init.js",
    "chars": 19237,
    "preview": "(function($) {\n  \"use strict\" \n\n\t\n\t/* function draw() {\n\t\t\n\t} */\n\n var dzSparkLine = function(){\n\tlet draw = Chart.contr"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/clock-picker-init.js",
    "chars": 566,
    "preview": "(function($) {\n    \"use strict\"\n\n    // Clock pickers\n    var input = $('#single-input').clockpicker({\n        placement"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/datatables.init.js",
    "chars": 6340,
    "preview": "let dataSet = [\n    [ \"Tiger Nixon\", \"System Architect\", \"Edinburgh\", \"5421\", \"2011/04/25\", \"$320,800\" ],\n    [ \"Garrett"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/flot-init.js",
    "chars": 10257,
    "preview": "(function($) {\n   \"use strict\"\n\n\n var dzChartlist = function(){\n\t\n\tvar screenWidth = $(window).width();\n\n\tvar flotBar1 ="
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/fullcalendar-init.js",
    "chars": 3247,
    "preview": "\"use strict\"\n document.addEventListener('DOMContentLoaded', function() {\n\n\t\t/* initialize the external events\n\t\t--------"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/jquery-asColorPicker.init.js",
    "chars": 267,
    "preview": "(function($) {\n    \"use strict\"\n    \n    // Colorpicker\n    $(\".as_colorpicker\").asColorPicker();\n    $(\".complex-colorp"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/jquery.validate-init.js",
    "chars": 484,
    "preview": "(function () {\n  'use strict'\n\n  // Fetch all the forms we want to apply custom Bootstrap validation styles to\n  var for"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/jqvmap-init.js",
    "chars": 2308,
    "preview": "(function($) {\n    \"use strict\" \n\n\n var dzVectorMap = function(){\n\t\n\tvar screenWidth = $(window).width();\n\t\n\tvar handleW"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/material-date-picker-init.js",
    "chars": 519,
    "preview": "(function($) {\n    \"use strict\"\n\n    // MAterial Date picker\n    $('#mdate').bootstrapMaterialDatePicker({\n        weekS"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/morris-init.js",
    "chars": 7644,
    "preview": "(function($) {\n    \"use strict\"\n\n\tvar dzMorris = function(){\n\t\t\n\t\tvar screenWidth = $(window).width();\n\t\t\n\t\tvar setChart"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/nestable-init.js",
    "chars": 882,
    "preview": "(function ($) {\n    \"use strict\"\n\n\n/*******************\nNestable\n*******************/\n\n    var e = function (e) {\n      "
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/nouislider-init.js",
    "chars": 29906,
    "preview": "(function($) {\n    \"use strict\"\n\n    //basic slider\n    let basicSlide = document.getElementById('basic-slider');\n    no"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/pickadate-init.js",
    "chars": 121,
    "preview": "(function($) {\n    \"use strict\"\n\n    //date picker classic default\n    $('.datepicker-default').pickadate();\n\n})(jQuery)"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/piety-init.js",
    "chars": 5229,
    "preview": "\n(function($) {\n    \"use strict\"\n\n\n/****************\nPiety chart\n*****************/\nvar dzPiety = function(){\n\t\n\tvar get"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/select2-init.js",
    "chars": 10141,
    "preview": "\n\n(function($) {\n  \"use strict\"\n  \n  // single select box\n  $(\"#single-select\").select2();\n\n  // multi select box\n  $('."
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/sparkline-init.js",
    "chars": 6451,
    "preview": "(function($) {\n    \"use strict\" \n   \n var dzSparkLine = function(){\n    \n\tvar screenWidth = $(window).width();\n\t\n\tfuncti"
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/sweetalert.init.js",
    "chars": 2723,
    "preview": "\"use strict\"\ndocument.querySelector(\".sweet-wrong\").onclick = function () { sweetAlert(\"Oops...\", \"Something went wrong "
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/toastr-init.js",
    "chars": 26532,
    "preview": "(function ($) {\n    \"use strict\"\n\n\n/*******************\nToastr\n*******************/\n\n    $(\"#toastr-success-top-right\")."
  },
  {
    "path": "src/main/resources/static/backend/js/plugins-init/widgets-script-init.js",
    "chars": 27982,
    "preview": "(function($) {\n    \"use strict\" \n\n var dzChartlist = function(){\n\t\n\tvar screenWidth = $(window).width();\n\t\n\tvar activity"
  },
  {
    "path": "src/main/resources/static/backend/js/styleSwitcher.js",
    "chars": 23124,
    "preview": "\n\"use strict\"\nfunction addSwitcher()\n{\n\tvar dzSwitcher = '<div class=\"sidebar-right\"><div class=\"bg-overlay\"></div><a cl"
  },
  {
    "path": "src/main/resources/static/backend/vendor/jquery-nice-select/css/nice-select.css",
    "chars": 4048,
    "preview": ".nice-select {\n  -webkit-tap-highlight-color: transparent;\n  background-color: #fff;\n  border-radius: 5px;\n  border: sol"
  },
  {
    "path": "src/main/resources/static/backend/vendor/owl-carousel/owl.carousel.css",
    "chars": 3506,
    "preview": "/* Owl Carousel - Animate Plugin */\r\r\n.owl-carousel,.owl-carousel .owl-item{-webkit-tap-highlight-color:transparent;posi"
  },
  {
    "path": "src/main/resources/static/backend/vendor/owl-carousel/owl.carousel.js",
    "chars": 42766,
    "preview": "/**\r * Owl Carousel v2.2.1\r * Copyright 2013-2017 David Deutsch\r * Licensed under  ()\r */\r!function(a,b,c,d){function e("
  },
  {
    "path": "src/main/resources/static/backend/vendor/perfect-scrollbar/css/perfect-scrollbar.css",
    "chars": 2598,
    "preview": "/*\n * Container style\n */\n.ps {\n  overflow: hidden !important;\n  overflow-anchor: none;\n  -ms-overflow-style: none;\n  to"
  },
  {
    "path": "src/main/resources/static/css/animate.css",
    "chars": 23848,
    "preview": "@charset \"UTF-8\";\n\n/*!\n * animate.css -http://daneden.me/animate\n * Version - 3.5.2\n * Licensed under the MIT license - "
  },
  {
    "path": "src/main/resources/static/css/dark-mode.css",
    "chars": 4179,
    "preview": "/**\n * 暗黑模式样式\n * Dark Mode Styles\n */\n\n/* 基础变量 */\n:root {\n    --bg-primary: #ffffff;\n    --bg-secondary: #f8f9fa;\n    --"
  },
  {
    "path": "src/main/resources/static/css/donate.css",
    "chars": 1919,
    "preview": ".content{width:80%;margin:200px auto;}\n.hide_box{z-index:999;filter:alpha(opacity=50);background:#666;opacity: 0.5;-moz-"
  },
  {
    "path": "src/main/resources/static/css/font.css",
    "chars": 505,
    "preview": "@font-face {\n  font-family: 'iconfont';\n  src: url('../fonts/iconfont.eot');\n  src: url('../fonts/iconfont.eot?#iefix') "
  },
  {
    "path": "src/main/resources/static/css/foreBlog.css",
    "chars": 44202,
    "preview": "html {\n    height: 100%;\n}\nbody {\n    background-color: #f4f5f7;\n    min-height: 100%;\n    display: flex;\n    flex-direc"
  },
  {
    "path": "src/main/resources/static/css/friend.css",
    "chars": 8979,
    "preview": "\n\n\n.m-bg{\n    width: 100%;\n    height: 650px;\n    object-fit: cover;\n}\n.m-padded-mini {\n    padding: 0.2em !important;\n}"
  },
  {
    "path": "src/main/resources/static/css/timeline.css",
    "chars": 2127,
    "preview": "*,\n*::before,\n*::after {\n  margin: 0;\n  padding: 0;\n  box-sizing: border-box;\n}\n\n.timeline ul li {\n  list-style-type: no"
  },
  {
    "path": "src/main/resources/static/css/typo.css",
    "chars": 6125,
    "preview": "@charset \"utf-8\";\n\n.typo p {\n  font-size: 16px;\n  font-weight: 300;\n  line-height: 1.8;\n  text-align: justify;\n}\n\n.typo "
  },
  {
    "path": "src/main/resources/static/js/article.js",
    "chars": 548,
    "preview": "let self;\n\n$(function () {\n\n    window.addEventListener('scroll', function() {\n        var t = $('body, html').scrollTop"
  },
  {
    "path": "src/main/resources/static/js/canvas-ribbon.js",
    "chars": 13892,
    "preview": "(function (name, factory) {\n    if (typeof window === \"object\") {\n        window[name] = factory();\n    }\n\n})(\"Ribbons\","
  },
  {
    "path": "src/main/resources/static/js/category.js",
    "chars": 697,
    "preview": "\nvar self;\n\nfunction jumpPage(pageNumber) {\n    if (pageNumber==self.data.currentPage){\n        return;\n    }\n    self.l"
  },
  {
    "path": "src/main/resources/static/js/error.js",
    "chars": 400,
    "preview": "$(function(){\n\n    $('.mobileButton').click(function(){\n        $(\".navItem\").toggleClass(\"mobileHidden\");\n    });\n\n    "
  },
  {
    "path": "src/main/resources/static/js/foreBlog.js",
    "chars": 8473,
    "preview": "/* 页面加载完成后隐藏预加载遮罩 */\n$(window).on('load', function(){\n    var preloader = document.getElementById('page-preloader');\n   "
  },
  {
    "path": "src/main/resources/static/js/home.js",
    "chars": 1305,
    "preview": "var self;\n\nfunction jumpPage(pageNumber) {\n    if (pageNumber==self.data.currentPage){\n        return;\n    }\n    self.li"
  },
  {
    "path": "src/main/resources/static/js/jquery.js",
    "chars": 147844,
    "preview": "/*! jQuery v2.0.0 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license\n//@ sourceMappingURL=jquery.min.map\n*/\n("
  },
  {
    "path": "src/main/resources/static/js/tags.js",
    "chars": 5696,
    "preview": "$(function(){\n\n    var radius = 150;\n    var dtr = Math.PI / 180;\n    var d = 300;\n    var mcList = [];\n    var active ="
  },
  {
    "path": "src/main/resources/static/js/theme.js",
    "chars": 1311,
    "preview": "/**\n * 暗黑模式切换\n * Dark Mode Toggle\n */\n(function() {\n    'use strict';\n    \n    const STORAGE_KEY = 'blog-theme';\n    con"
  },
  {
    "path": "src/main/resources/static/js/whatwg-fetch@2.0.3_fetch.js",
    "chars": 12680,
    "preview": "(function(self) {\n  'use strict';\n\n  if (self.fetch) {\n    return\n  }\n\n  var support = {\n    searchParams: 'URLSearchPar"
  },
  {
    "path": "src/main/resources/static/lib/editormd/css/editormd.css",
    "chars": 78137,
    "preview": "/*\n * Editor.md\n *\n * @file        editormd.css \n * @version     v1.5.0 \n * @description Open source online markdown edi"
  },
  {
    "path": "src/main/resources/static/lib/editormd/css/editormd.logo.css",
    "chars": 2067,
    "preview": "/*\n * Editor.md\n *\n * @file        editormd.logo.css \n * @version     v1.5.0 \n * @description Open source online markdow"
  },
  {
    "path": "src/main/resources/static/lib/editormd/css/editormd.preview.css",
    "chars": 56660,
    "preview": "/*\n * Editor.md\n *\n * @file        editormd.preview.css \n * @version     v1.5.0 \n * @description Open source online mark"
  },
  {
    "path": "src/main/resources/static/lib/editormd/editormd.js",
    "chars": 154540,
    "preview": "/*\n * Editor.md\n *\n * @file        editormd.js \n * @version     v1.5.0 \n * @description Open source online markdown edit"
  },
  {
    "path": "src/main/resources/static/lib/editormd/languages/en.js",
    "chars": 5201,
    "preview": "(function(){\n    var factory = function (exports) {\n        var lang = {\n            name : \"en\",\n            descriptio"
  },
  {
    "path": "src/main/resources/static/lib/editormd/languages/zh-tw.js",
    "chars": 4532,
    "preview": "(function(){\n    var factory = function (exports) {\n        var lang = {\n            name : \"zh-tw\",\n            descrip"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/AUTHORS",
    "chars": 5704,
    "preview": "List of CodeMirror contributors. Updated before every release.\n\n4r2r\nAaron Brooks\nAbdelouahab\nAbe Fettig\nAdam Ahmed\nAdam"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/LICENSE",
    "chars": 1094,
    "preview": "Copyright (C) 2014 by Marijn Haverbeke <marijnh@gmail.com> and others\n\nPermission is hereby granted, free of charge, to "
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/README.md",
    "chars": 784,
    "preview": "# CodeMirror\n[![Build Status](https://travis-ci.org/codemirror/CodeMirror.svg)](https://travis-ci.org/codemirror/CodeMir"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/comment/comment.js",
    "chars": 8008,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/comment/continuecomment.js",
    "chars": 3399,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/dialog/dialog.css",
    "chars": 502,
    "preview": ".CodeMirror-dialog {\n  position: absolute;\n  left: 0; right: 0;\n  background: white;\n  z-index: 15;\n  padding: .1em .8em"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/dialog/dialog.js",
    "chars": 4875,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/display/fullscreen.css",
    "chars": 116,
    "preview": ".CodeMirror-fullscreen {\n  position: fixed;\n  top: 0; left: 0; right: 0; bottom: 0;\n  height: auto;\n  z-index: 9;\n}\n"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/display/fullscreen.js",
    "chars": 1494,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/display/panel.js",
    "chars": 3102,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/display/placeholder.js",
    "chars": 1971,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/display/rulers.js",
    "chars": 2134,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/edit/closebrackets.js",
    "chars": 6462,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/edit/closetag.js",
    "chars": 7590,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/edit/continuelist.js",
    "chars": 1752,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/edit/matchbrackets.js",
    "chars": 5254,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/edit/matchtags.js",
    "chars": 2355,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/edit/trailingspace.js",
    "chars": 1003,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/brace-fold.js",
    "chars": 3904,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/comment-fold.js",
    "chars": 1999,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/foldcode.js",
    "chars": 4693,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/foldgutter.css",
    "chars": 435,
    "preview": ".CodeMirror-foldmarker {\n  color: blue;\n  text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1p"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/foldgutter.js",
    "chars": 4550,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/indent-fold.js",
    "chars": 1627,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/markdown-fold.js",
    "chars": 1605,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/fold/xml-fold.js",
    "chars": 6570,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/anyword-hint.js",
    "chars": 1653,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/css-hint.js",
    "chars": 1951,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/html-hint.js",
    "chars": 11341,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/javascript-hint.js",
    "chars": 6163,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/show-hint.css",
    "chars": 662,
    "preview": ".CodeMirror-hints {\n  position: absolute;\n  z-index: 10;\n  overflow: hidden;\n  list-style: none;\n\n  margin: 0;\n  padding"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/show-hint.js",
    "chars": 14402,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/sql-hint.js",
    "chars": 7307,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/hint/xml-hint.js",
    "chars": 4735,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/lint/coffeescript-lint.js",
    "chars": 1270,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/lint/css-lint.js",
    "chars": 1146,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/lint/javascript-lint.js",
    "chars": 4452,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/lint/json-lint.js",
    "chars": 954,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/lint/lint.css",
    "chars": 3012,
    "preview": "/* The lint marker gutter */\n.CodeMirror-lint-markers {\n  width: 16px;\n}\n\n.CodeMirror-lint-tooltip {\n  background-color:"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/lint/lint.js",
    "chars": 7115,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/lint/yaml-lint.js",
    "chars": 848,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/merge/merge.css",
    "chars": 3235,
    "preview": ".CodeMirror-merge {\n  position: relative;\n  border: 1px solid #ddd;\n  white-space: pre;\n}\n\n.CodeMirror-merge, .CodeMirro"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/merge/merge.js",
    "chars": 27266,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  },
  {
    "path": "src/main/resources/static/lib/editormd/lib/codemirror/addon/mode/loadmode.js",
    "chars": 2277,
    "preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
  }
]

// ... and 327 more files (download for full content)

About this extraction

This page contains the full source code of the laowenruo/Spring-Blog GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 527 files (4.6 MB), approximately 1.3M tokens, and a symbol index with 1987 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!