├── .github └── workflows │ └── generator_docs.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.en_US.md ├── README.md ├── docker-compose.yml ├── docs ├── .gitignore ├── assembly.xml ├── pom.xml └── src │ └── main │ └── asciidoc │ ├── api-doc.asciidoc │ ├── api-gettoken.asciidoc │ ├── api-request-demo.asciidoc │ ├── api-return-object.asciidoc │ ├── base-config.asciidoc │ ├── case.asciidoc │ ├── deploy.asciidoc │ ├── email-config.asciidoc │ ├── es-config.asciidoc │ ├── getting-started.asciidoc │ ├── images │ ├── 20210308113951.png │ ├── 20230310.jpg │ ├── 20230310104837.png │ ├── 7C56195B1FE6F942649D30D65416EE80.jpg │ ├── QQ20190103-135005.png │ ├── QQ20190103-135046.png │ ├── QQ20190103-154507.png │ ├── QQ20190103-154553.png │ ├── QQ20190103-155656.png │ ├── QQ20190107-135811.png │ ├── QQ20190107-135903.png │ ├── QQ20190107-140155.png │ ├── QQ20190109-111822.png │ ├── QQ20190123-103144.png │ ├── QQ20190131-173707.png │ ├── QQ20190418-153321.png │ ├── QQ20190725-111655.png │ ├── TIM20190417185246.png │ ├── TIM20190417185315.png │ ├── TIM20190417185600.png │ └── captcha.gif │ ├── index.asciidoc │ ├── introduction.asciidoc │ ├── oauth-config.asciidoc │ ├── qa.asciidoc │ ├── ready.asciidoc │ ├── redis-config.asciidoc │ ├── sms-config.asciidoc │ ├── telegram-config.asciidoc │ ├── theme-dev.asciidoc │ ├── theme.asciidoc │ ├── upload-config.asciidoc │ ├── videos │ └── 1678413054469.mp4 │ └── ws-config.asciidoc ├── fronts └── readme.txt ├── plugins ├── README.asciidoc ├── comment-layer-plugin │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── co │ │ └── yiiu │ │ └── pybbs │ │ └── plugin │ │ └── CommentLayerPlugin.java ├── redis-cache-plugin │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── co │ │ └── yiiu │ │ └── pybbs │ │ └── plugin │ │ ├── RedisCachePlugin.java │ │ └── RedisService.java └── theme-simple-plugin │ ├── pom.xml │ └── src │ └── main │ └── resources │ ├── static │ └── theme │ │ └── simple │ │ └── css │ │ ├── app.css │ │ ├── app.css.map │ │ └── app.scss │ └── templates │ └── theme │ └── simple │ ├── comment │ └── edit.ftl │ ├── components │ ├── loading.ftl │ ├── notification.ftl │ ├── topics.ftl │ ├── upload.ftl │ ├── user_collects.ftl │ ├── user_comments.ftl │ └── user_topics.ftl │ ├── error.ftl │ ├── index.ftl │ ├── layout │ ├── footer.ftl │ ├── header.ftl │ └── layout.ftl │ ├── login.ftl │ ├── notifications.ftl │ ├── register.ftl │ ├── search.ftl │ ├── tag │ ├── tag.ftl │ └── tags.ftl │ ├── top100.ftl │ ├── topic │ ├── create.ftl │ ├── detail.ftl │ └── edit.ftl │ └── user │ ├── collects.ftl │ ├── comments.ftl │ ├── profile.ftl │ ├── settings.ftl │ └── topics.ftl ├── pom.xml ├── shutdown.sh ├── snapshot ├── 1.png ├── 2.png ├── 3.png ├── alipay.png └── wechat.png ├── src ├── main │ ├── java │ │ └── co │ │ │ └── yiiu │ │ │ └── pybbs │ │ │ ├── PybbsApplication.java │ │ │ ├── config │ │ │ ├── DataSourceConfig.java │ │ │ ├── DataSourceHelper.java │ │ │ ├── FlywayConfig.java │ │ │ ├── FreemarkerConfig.java │ │ │ ├── GlobalCorsConfiguration.java │ │ │ ├── MybatisPlusConfig.java │ │ │ ├── RestTemplateConfig.java │ │ │ ├── ServerRunner.java │ │ │ ├── ShiroConfig.java │ │ │ ├── ShiroTag.java │ │ │ ├── SiteConfig.java │ │ │ ├── WebMvcConfig.java │ │ │ ├── realm │ │ │ │ ├── MyCredentialsMatcher.java │ │ │ │ ├── MyShiroFilter.java │ │ │ │ └── MyShiroRealm.java │ │ │ ├── service │ │ │ │ ├── BaseService.java │ │ │ │ ├── EmailService.java │ │ │ │ ├── SensitiveWordFilterService.java │ │ │ │ ├── SmsService.java │ │ │ │ └── TelegramBotService.java │ │ │ └── websocket │ │ │ │ ├── MessageDecoder.java │ │ │ │ ├── MessageEncoder.java │ │ │ │ ├── MyWebSocket.java │ │ │ │ └── WebSocketConfig.java │ │ │ ├── controller │ │ │ ├── admin │ │ │ │ ├── AdminUserAdminController.java │ │ │ │ ├── BaseAdminController.java │ │ │ │ ├── CommentAdminController.java │ │ │ │ ├── IndexAdminController.java │ │ │ │ ├── PermissionAdminController.java │ │ │ │ ├── RoleAdminController.java │ │ │ │ ├── SensitiveWordAdminController.java │ │ │ │ ├── SystemConfigAdminController.java │ │ │ │ ├── TagAdminController.java │ │ │ │ ├── TopicAdminController.java │ │ │ │ └── UserAdminController.java │ │ │ ├── api │ │ │ │ ├── BaseApiController.java │ │ │ │ ├── CollectApiController.java │ │ │ │ ├── CommentApiController.java │ │ │ │ ├── IndexApiController.java │ │ │ │ ├── NotificationApiController.java │ │ │ │ ├── SettingsApiController.java │ │ │ │ ├── TelegramBotController.java │ │ │ │ ├── TopicApiController.java │ │ │ │ └── UserApiController.java │ │ │ └── front │ │ │ │ ├── BaseController.java │ │ │ │ ├── CommentController.java │ │ │ │ ├── CommonController.java │ │ │ │ ├── IndexController.java │ │ │ │ ├── OAuthController.java │ │ │ │ ├── TopicController.java │ │ │ │ └── UserController.java │ │ │ ├── directive │ │ │ ├── NotificationsDirective.java │ │ │ ├── OtherTopicDirective.java │ │ │ ├── ScoreDirective.java │ │ │ ├── SearchDirective.java │ │ │ ├── SocialDirective.java │ │ │ ├── TagsDirective.java │ │ │ ├── TopicCommentsDirective.java │ │ │ ├── TopicListDirective.java │ │ │ ├── UserCollectsDirective.java │ │ │ ├── UserCommentsDirective.java │ │ │ └── UserTopicsDirective.java │ │ │ ├── exception │ │ │ ├── ApiAssert.java │ │ │ ├── ApiException.java │ │ │ └── GlobalExceptionHandler.java │ │ │ ├── hook │ │ │ ├── CommentServiceHook.java │ │ │ ├── FileUtilHook.java │ │ │ ├── IndexedServiceHook.java │ │ │ ├── TopicServiceHook.java │ │ │ └── UserServiceHook.java │ │ │ ├── interceptor │ │ │ ├── CommonInterceptor.java │ │ │ └── UserInterceptor.java │ │ │ ├── mapper │ │ │ ├── AdminUserMapper.java │ │ │ ├── AdminUserMapper.xml │ │ │ ├── CodeMapper.java │ │ │ ├── CollectMapper.java │ │ │ ├── CollectMapper.xml │ │ │ ├── CommentMapper.java │ │ │ ├── CommentMapper.xml │ │ │ ├── NotificationMapper.java │ │ │ ├── NotificationMapper.xml │ │ │ ├── OAuthUserMapper.java │ │ │ ├── PermissionMapper.java │ │ │ ├── RoleMapper.java │ │ │ ├── RolePermissionMapper.java │ │ │ ├── SensitiveWordMapper.java │ │ │ ├── SystemConfigMapper.java │ │ │ ├── SystemConfigMapper.xml │ │ │ ├── TagMapper.java │ │ │ ├── TagMapper.xml │ │ │ ├── TopicMapper.java │ │ │ ├── TopicMapper.xml │ │ │ ├── TopicTagMapper.java │ │ │ ├── UserMapper.java │ │ │ └── UserMapper.xml │ │ │ ├── model │ │ │ ├── AdminUser.java │ │ │ ├── Code.java │ │ │ ├── Collect.java │ │ │ ├── Comment.java │ │ │ ├── Notification.java │ │ │ ├── OAuthUser.java │ │ │ ├── Permission.java │ │ │ ├── Role.java │ │ │ ├── RolePermission.java │ │ │ ├── SensitiveWord.java │ │ │ ├── SystemConfig.java │ │ │ ├── Tag.java │ │ │ ├── Topic.java │ │ │ ├── TopicTag.java │ │ │ ├── User.java │ │ │ └── vo │ │ │ │ ├── CommentsByTopic.java │ │ │ │ ├── UserWithWebSocketVO.java │ │ │ │ └── package-info.java │ │ │ ├── plugin │ │ │ ├── CommentLayerPlugin.java │ │ │ ├── ElasticSearchPlugin.java │ │ │ ├── ElasticSearchService.java │ │ │ ├── FileUploadPlugin.java │ │ │ ├── RedisCachePlugin.java │ │ │ ├── RedisService.java │ │ │ └── SocialPlugin.java │ │ │ ├── service │ │ │ ├── IAdminUserService.java │ │ │ ├── ICodeService.java │ │ │ ├── ICollectService.java │ │ │ ├── ICommentService.java │ │ │ ├── IIndexedService.java │ │ │ ├── INotificationService.java │ │ │ ├── IOAuthUserService.java │ │ │ ├── IPermissionService.java │ │ │ ├── IRolePermissionService.java │ │ │ ├── IRoleService.java │ │ │ ├── ISensitiveWordService.java │ │ │ ├── ISystemConfigService.java │ │ │ ├── ITagService.java │ │ │ ├── ITopicService.java │ │ │ ├── ITopicTagService.java │ │ │ ├── IUserService.java │ │ │ └── impl │ │ │ │ ├── AdminUserService.java │ │ │ │ ├── CodeService.java │ │ │ │ ├── CollectService.java │ │ │ │ ├── CommentService.java │ │ │ │ ├── IndexedService.java │ │ │ │ ├── NotificationService.java │ │ │ │ ├── OAuthUserService.java │ │ │ │ ├── PermissionService.java │ │ │ │ ├── RolePermissionService.java │ │ │ │ ├── RoleService.java │ │ │ │ ├── SensitiveWordService.java │ │ │ │ ├── SystemConfigService.java │ │ │ │ ├── TagService.java │ │ │ │ ├── TopicService.java │ │ │ │ ├── TopicTagService.java │ │ │ │ └── UserService.java │ │ │ └── util │ │ │ ├── BaseModel.java │ │ │ ├── Constants.java │ │ │ ├── CookieUtil.java │ │ │ ├── DateUtil.java │ │ │ ├── FileUtil.java │ │ │ ├── HashUtil.java │ │ │ ├── HttpUtil.java │ │ │ ├── IpUtil.java │ │ │ ├── JsonUtil.java │ │ │ ├── LocaleMessageSourceUtil.java │ │ │ ├── MD5Util.java │ │ │ ├── MarkdownUtil.java │ │ │ ├── Message.java │ │ │ ├── MyPage.java │ │ │ ├── Result.java │ │ │ ├── SecurityUtil.java │ │ │ ├── SensitiveWordUtil.java │ │ │ ├── SpringContextUtil.java │ │ │ ├── StringUtil.java │ │ │ ├── bcrypt │ │ │ ├── BCrypt.java │ │ │ └── BCryptPasswordEncoder.java │ │ │ ├── captcha │ │ │ ├── Captcha.java │ │ │ ├── Encoder.java │ │ │ ├── GifCaptcha.java │ │ │ ├── GifEncoder.java │ │ │ ├── Quant.java │ │ │ ├── Randoms.java │ │ │ ├── SpecCaptcha.java │ │ │ ├── Streams.java │ │ │ └── package-info.java │ │ │ └── identicon │ │ │ ├── Identicon.java │ │ │ └── generator │ │ │ ├── IBaseGenerator.java │ │ │ └── impl │ │ │ ├── DefaultGenerator.java │ │ │ └── MyGenerator.java │ └── resources │ │ ├── application-dev.yml │ │ ├── application-docker.yml │ │ ├── application-prod.yml │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── db │ │ └── migration │ │ │ ├── V1.10__delete_system_config_search.sql │ │ │ ├── V1.11__update_system_config_key.sql │ │ │ ├── V1.12__add_indexes.sql │ │ │ ├── V1.13__add_social_config.sql │ │ │ ├── V1.14__add_system_config_user_need_active.sql │ │ │ ├── V1.15__modify_content_character.sql │ │ │ ├── V1.16__add_column_content_style.sql │ │ │ ├── V1.17__add_cloud_storage.sql │ │ │ ├── V1.18__add_tgbot.sql │ │ │ ├── V1.1__user_add_field.sql │ │ │ ├── V1.2__update_system_config_default_theme.sql │ │ │ ├── V1.3__delete_system_config_redis_ssl.sql │ │ │ ├── V1.4__alter_code.sql │ │ │ ├── V1.5__add_sensitive_word.sql │ │ │ ├── V1.6__add_permission_about_sensitive_word.sql │ │ │ ├── V1.7__alter_oauth_user.sql │ │ │ ├── V1.8__add_sms_region_id.sql │ │ │ ├── V1.9__update_system_config_ws.sql │ │ │ └── V1__initialize.sql │ │ ├── i18n │ │ ├── message.properties │ │ ├── message_en_US.properties │ │ └── message_zh_CN.properties │ │ ├── logback-spring.xml │ │ ├── static │ │ ├── admin │ │ │ └── js │ │ │ │ └── app.min.js │ │ └── theme │ │ │ └── default │ │ │ ├── css │ │ │ └── app.css │ │ │ ├── js │ │ │ ├── app.js │ │ │ └── codemirror.js │ │ │ └── libs │ │ │ └── layer │ │ │ ├── layer.js │ │ │ ├── mobile │ │ │ ├── layer.js │ │ │ └── need │ │ │ │ └── layer.css │ │ │ └── theme │ │ │ ├── default │ │ │ ├── icon-ext.png │ │ │ ├── icon.png │ │ │ ├── layer.css │ │ │ ├── loading-0.gif │ │ │ ├── loading-1.gif │ │ │ └── loading-2.gif │ │ │ └── moon │ │ │ ├── default.png │ │ │ └── style.css │ │ └── templates │ │ ├── admin │ │ ├── admin_user │ │ │ ├── add.ftl │ │ │ ├── edit.ftl │ │ │ └── list.ftl │ │ ├── comment │ │ │ ├── edit.ftl │ │ │ └── list.ftl │ │ ├── index.ftl │ │ ├── layout │ │ │ ├── footer.ftl │ │ │ ├── header.ftl │ │ │ ├── layout.ftl │ │ │ ├── menu.ftl │ │ │ └── paginate.ftl │ │ ├── login.ftl │ │ ├── permission │ │ │ └── list.ftl │ │ ├── role │ │ │ ├── add.ftl │ │ │ ├── edit.ftl │ │ │ └── list.ftl │ │ ├── sensitive_word │ │ │ └── list.ftl │ │ ├── system │ │ │ └── edit.ftl │ │ ├── tag │ │ │ ├── edit.ftl │ │ │ └── list.ftl │ │ ├── topic │ │ │ ├── edit.ftl │ │ │ └── list.ftl │ │ └── user │ │ │ ├── edit.ftl │ │ │ └── list.ftl │ │ └── theme │ │ └── default │ │ ├── comment │ │ └── edit.ftl │ │ ├── components │ │ ├── author.ftl │ │ ├── create_topic_guide.ftl │ │ ├── editor.ftl │ │ ├── forget_password.ftl │ │ ├── github_repos.ftl │ │ ├── markdown_guide.ftl │ │ ├── mobile_login.ftl │ │ ├── notification.ftl │ │ ├── other_topic.ftl │ │ ├── paginate.ftl │ │ ├── score.ftl │ │ ├── token.ftl │ │ ├── topic_comments.ftl │ │ ├── topics.ftl │ │ ├── upload.ftl │ │ ├── user_comments.ftl │ │ ├── user_info.ftl │ │ ├── user_topics.ftl │ │ ├── websocket.ftl │ │ └── welcome.ftl │ │ ├── error.ftl │ │ ├── index.ftl │ │ ├── layout │ │ ├── footer.ftl │ │ ├── header.ftl │ │ └── layout.ftl │ │ ├── login.ftl │ │ ├── notifications.ftl │ │ ├── register.ftl │ │ ├── search.ftl │ │ ├── tag │ │ ├── tag.ftl │ │ └── tags.ftl │ │ ├── top100.ftl │ │ ├── topic │ │ ├── create.ftl │ │ ├── detail.ftl │ │ └── edit.ftl │ │ └── user │ │ ├── collects.ftl │ │ ├── comments.ftl │ │ ├── profile.ftl │ │ ├── settings.ftl │ │ └── topics.ftl └── test │ └── java │ └── co │ └── yiiu │ └── pybbs │ ├── PybbsApplicationTests.java │ └── util │ └── StringUtilTest.java └── start.sh /.github/workflows/generator_docs.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v2 24 | - uses: actions/setup-java@v1 25 | with: 26 | java-version: 1.8 27 | # Runs a set of commands using the runners shell 28 | - name: Compile Docs 29 | run: | 30 | mvn clean compile --file docs/pom.xml 31 | cp -r ./docs/target/generated-docs/ ../docs 32 | - name: Deploy Docs 33 | uses: peaceiris/actions-gh-pages@v3 34 | with: 35 | github_token: ${{ secrets.push_token }} 36 | publish_dir: ./docs/target/generated-docs 37 | 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.war 8 | *.ear 9 | 10 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 11 | hs_err_pid* 12 | 13 | .idea/ 14 | *.iml 15 | *.log 16 | target/ 17 | out/ 18 | .DS_Store 19 | .apt_generated/ 20 | .project 21 | .factorypath 22 | .settings 23 | .classpath 24 | docs/_site 25 | indexes/ 26 | data/ 27 | .settings/ 28 | logs/ 29 | .vscode/ 30 | mysql 31 | static/upload/ 32 | .sass-cache/ 33 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.5.4-jdk-8 2 | 3 | ENV LANG C.UTF-8 -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | mysql: 4 | container_name: bbs-mysql 5 | image: mysql/mysql-server:5.7 6 | environment: 7 | MYSQL_DATABASE: pybbs 8 | MYSQL_ROOT_PASSWORD: root 9 | MYSQL_ROOT_HOST: '%' 10 | TZ: Asia/Shanghai 11 | expose: 12 | - "3306" 13 | volumes: 14 | - ./src/main/resources/db/migration:/docker-entrypoint-initdb.d 15 | - ./mysql/mysql_data:/var/lib/mysql 16 | restart: always 17 | 18 | server: 19 | container_name: bbs-server 20 | build: . 21 | working_dir: /app 22 | environment: 23 | TZ: Asia/Shanghai 24 | volumes: 25 | - ./:/app 26 | - ~/.m2:/root/.m2 27 | - ./logs:/app/logs 28 | - ./static:/app/static 29 | ports: 30 | - "8080:8080" 31 | command: mvn clean spring-boot:run -Dspring-boot.run.profiles=docker -Dmaven.test.skip=true 32 | depends_on: 33 | - mysql 34 | restart: always 35 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | *.iml 4 | *~ 5 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/api-gettoken.asciidoc: -------------------------------------------------------------------------------- 1 | === 获取token 2 | 3 | *系统默认开启了cors访问,任何源都可以访问 `/api/**` 下的资源,如果想关闭的话,可以通过修改源码的方式关闭,源码位置 `co.yiiu.pybbs.config.WebMvcConfig`* 4 | 5 | 有的接口请求要求带上用户的`token`参数,这个token是在用户注册的时候自动生成的,可以在个人设置页面重新生成 6 | 7 | token的获取方式: 8 | 9 | 用户登录上论坛,打开页面最上方的 设置,在页面右边可以查看到自己的token,如下图 10 | 11 | image:./images/QQ20190109-111822.png[] 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/api-request-demo.asciidoc: -------------------------------------------------------------------------------- 1 | === 请求接口示例 2 | 3 | pybbs上的接口风格已经全都换成`RESTFUL`风格的了,调用方式也有了相应的调整 4 | 5 | 1. 所有需要传token的接口,token参数要放在请求头里(headers) 6 | 2. 所有需要传参数的接口,参数都以 json 的形式传递 7 | 3. 请求不单单是get, post了,还加入了put, delete,请仔细查看接口文档 8 | 9 | 下面给一个发帖的jQuery调用示例: 10 | 11 | [source,js,indent=0] 12 | ---- 13 | $.ajax({ 14 | url: '/api/topic', 15 | type: 'post', 16 | cache: false, 17 | async: false, 18 | headers: { 19 | 'token': '8f2e6b0d-5a7a-44eb-9c96-4f87d55c212e' 20 | }, 21 | contentType: 'application/json', 22 | data: JSON.stringify({ 23 | title: title, 24 | content: content, 25 | tags: tags, 26 | }), 27 | success: function(data) { 28 | if (data.code === 200) { 29 | window.location.href = "/topic/" + data.detail.id 30 | } else { 31 | alert(data.description); 32 | } 33 | } 34 | }) 35 | ---- 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/api-return-object.asciidoc: -------------------------------------------------------------------------------- 1 | === 接口返回对象 2 | 3 | 接口返回对象就只有一个 `Result` 4 | 5 | 这个类是在程序里自定义的,共三个属性 6 | 7 | [source,java,indent=0] 8 | ---- 9 | public class Result { 10 | 11 | private Integer code; 12 | private String description; 13 | private Object detail; 14 | 15 | // getter, setter 16 | } 17 | ---- 18 | 19 | - code : 返回时的状态值,成功:200, 失败:201 20 | - description: 失败时的一些描述信息放在这个属性里 21 | - detail: 一般放成功后的返回值,它是一个Object类型的属性,可以放任何对象 22 | 23 | === 接口返回分页对象 24 | 25 | 如果接口涉及到分页的话,就会返回 `Result(IPage)` 就是将查询后封装好的分页对象放在Result对象的detail属性里,再转成json返给前端 26 | 27 | IPage对象是MyBatis-Plus内置的一个分页对象,其中调用接口可能会用到的属性有如下几个 28 | 29 | - records: 查询出的列表对象 30 | - pages: 分页后的总页数 31 | - total: 总条数 32 | - current: 当前页数 33 | - size: 每页条数 34 | 35 | 遗憾的是它没有像jpa那样封装两个属性 `last` `next` 这样就可以直接拿它们的值来判断是不是第一页或最后一页了 36 | 37 | 不过也可以通过 `current` 和 `pages` 来判断是第一页还是最后一页 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/base-config.asciidoc: -------------------------------------------------------------------------------- 1 | === 基本配置 2 | 3 | 这版的配置相对其它版本的配置要简单的多,唯一要配置的就是数据库相关的配置了(如果你的数据库用户名是root 密码是空的,数据库又是跟程序在一个机器上,那就不需要配置) 4 | 5 | 配置数据库连接找到配置文件修改如下配置 6 | 7 | - `src/main/resources/application-dev.yml` 开发启动时的配置文件 8 | - `src/main/resources/application-prod.yml` 部署时的配置文件 9 | - `src/main/resources/application-docker.yml` 通过docker启动时的配置文件 10 | 11 | [source,yml,indent=0] 12 | ---- 13 | datasource_driver: com.mysql.cj.jdbc.Driver 14 | datasource_url: jdbc:mysql://localhost:3306/pybbs?useSSL=false&characterEncoding=utf8 15 | datasource_username: root 16 | datasource_password: 17 | ---- 18 | 19 | 关于其它的配置,启动程序 -> 访问后台 -> 系统设置 如图 20 | 21 | image:./images/20230310.jpg[] 22 | 23 | *2023-03-10 新增Telegram接入收发消息功能* 24 | 25 | image:./images/20230310104837.png[] 26 | 27 | *2021-03-08 新增云存储功能* 28 | 29 | image:./images/20210308113951.png[] 30 | 31 | 有几个地方是必须要修改的,如下图中红框中的配置 32 | 33 | image:./images/QQ20190103-155656.png[] 34 | 35 | *注意:* 36 | 37 | 1. 网站的访问域名如果为 `http://example.com` 那么 `网站部署后访问的域名,注意这个后面没有 "/"` 这个说明下的内容就应该替换成 `http://example.com` 38 | 2. 第一步配置好域名后,cookie 的域名设置也要做相应的修改,否则用户登录的记录没法保存下来,在 `存cookie时用到的域名,要与网站部署后访问的域名一致` 这个说明下将 `localhost` 替换成 `example.com` 即可 39 | 3. 除了上面两条必须要修改外,网站的上传路径也要提前做好配置,具体参见 上传配置 40 | 41 | 其它的配置根据自己环境做相应的修改即可 42 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/case.asciidoc: -------------------------------------------------------------------------------- 1 | 欢迎大家提交使用pybbs部署的网站地址, 可以给我发邮件告知 py2qiuse@gmail.com 2 | 3 | |=== 4 | | 版本 | 域名 5 | 6 | | master | http://bbs.dudu-auto.com/ 7 | |=== 8 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/deploy.asciidoc: -------------------------------------------------------------------------------- 1 | == 部署步骤 2 | 3 | 1. 购买域名,域名提供商非常多,选一个自己喜欢的购买一个就可以了 4 | 2. 去服务器运营商购买服务器,建议阿里云,购买的时候看清区域,国内做论坛 *必须要备案的* ,不过阿里云也有国外的节点,购买的时候请注意 5 | 3. 安装java8,mysql5.7 6 | 4. 按照 快速开始 中的部署方法部署 7 | 8 | == nginx配置 9 | 10 | 如果你服务器上就只一个论坛项目,那直接将程序里的端口改成80即可,如果你还想折腾点其它的东西,那就要用到nginx做代理转发请求了,具体配置如下 11 | 12 | 假如 example.com 是你的域名,程序启动端口是 8080 ,配置如下 13 | 14 | [source,nginx,indent=0] 15 | ---- 16 | server { 17 | server_name example.com; 18 | location / { 19 | proxy_pass http://127.0.0.1:8080/; 20 | proxy_redirect off; 21 | proxy_set_header Host $host; 22 | proxy_set_header X-Real-IP $remote_addr; 23 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 24 | client_max_body_size 2m; 25 | client_body_buffer_size 128k; 26 | proxy_connect_timeout 300; 27 | proxy_send_timeout 300; 28 | proxy_read_timeout 300; 29 | proxy_buffers 32 32k; 30 | proxy_buffer_size 64k; 31 | proxy_busy_buffers_size 128k; 32 | } 33 | } 34 | ---- 35 | 36 | == frp映射配置 37 | 38 | 关于这个配置可以参见我的一篇博客 https://atjiu.github.io/2018/10/18/frp-tutorial/[利用frp内网穿透实现用自家电脑发布网站(不用买服务器了)] 39 | 40 | == 配置https 41 | 42 | https强烈推荐使用 letsencrypt 配置简单,主要是免费,唯一的缺点就是要3个月续一下时间,配置参见文档: https://atjiu.github.io/2016/08/28/letsencrypt-nginx-https/[letsencrypt结合nginx配置https备忘] 43 | 44 | 配置外网环境运气好,很快就可以搭建好,运气不好,折腾两天是常事,淡定慢慢配 45 | 46 | [TIP] 47 | *自己实在部署不好,朋也也可以代劳,不过是有偿的哦* 48 | 49 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/email-config.asciidoc: -------------------------------------------------------------------------------- 1 | === 邮箱配置 2 | 3 | 用户注册没有做邮箱验证,但用户可以在自己的设置页面添加邮箱 4 | 5 | 添加邮箱的时候,要发邮箱验证码,这时候就要在后台配置发邮件的邮箱配置了 6 | 7 | 我使用qq邮箱测试是没有问题的,具体配置方法,在后台已经内置了一些信息,稍做修改即可使用 8 | 9 | [WARNING] 10 | 我只测试了QQ邮箱, 配置邮箱请首先以QQ邮箱配置, 如果没有问题, 可以尝试换成其它平台的邮箱 11 | 12 | --- 13 | 14 | 发现还有好多人配不好qq的邮件,这里介绍一下qq邮件的密码获取方式 15 | 16 | 登录qq邮件 mail.qq.com ,然后打开设置 -> 帐户 往下翻,找到 `POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务` 17 | 18 | image:./images/TIM20190417185246.png[] 19 | 20 | image:./images/TIM20190417185315.png[] 21 | 22 | 点击 `生成授权码` 然后用手机扫描授权(直接使用手机qq的扫描功能就可以了),成功后,会有一个授权码,这个就是邮件的密码了 23 | 24 | image:./images/TIM20190417185600.png[] 25 | 26 | --- 27 | 28 | image:./images/QQ20190103-154507.png[] 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/es-config.asciidoc: -------------------------------------------------------------------------------- 1 | === ElasticSearch配置 2 | 3 | 程序内置了elasticsearch功能,不过要增加相应的配置才能用 4 | 5 | 此功能默认是关闭的,具体的配置方法如下 6 | 7 | 1. 下载elasticsearch,版本建议 6.5.3 我用的就是这个版本做的开发的 8 | 2. 安装ik分词插件,如果你懂命令行操作,可以执行这条命令 `cd elasticsearch-6.5.3/bin && ./elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.3/elasticsearch-analysis-ik-6.5.3.zip` 9 | 3. 启动程序,进入后台,打开系统设置 10 | 4. 具体操作如下图所示 11 | 12 | 打开搜索功能的开关 13 | 14 | image:./images/QQ20190103-135005.png[] 15 | 16 | 配置ES的连接(连接信息根据自己环境做相应的修改,不一定是图上所示的配置) 17 | 18 | image:./images/QQ20190103-135046.png[] 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/getting-started.asciidoc: -------------------------------------------------------------------------------- 1 | === 开发人员搭建 2 | 3 | - git clone https://github.com/atjiu/pybbs 4 | - 使用idea打开,项目用的是idea开发的,如果你对eclipse熟悉,也是可以的 5 | - idea打开它会自动构建项目,构建工具是maven 6 | - 修改配置文件 `src/main/resources/application-dev.yml` 里的数据库相关配置 7 | - 找到`co.yiiu.pybbs.PybbsApplication`类,直接运行main方法即可启动 8 | - 浏览器运行 `http://localhost:8080` , 后台地址 `http://localhost:8080/adminlogin` 后台用户名 admin 密码 123123 9 | 10 | [WARNING] 11 | 如果要自己打包,可使用命令:`mvn clean compile package` 进行打包,不要尝试使用其它方式打包 12 | 13 | === 非开发人员搭建 14 | 15 | [WARNING] 16 | release里打的包可能不是最新的 17 | 18 | - 首先保证你服务器上配置好了 java 环境,版本 jdk1.8 和 MySQL服务器,版本 5.7.x 其它可选环境配置参见 [网站准备工作](zh-cn/ready) 19 | - 然后下载最新的一键启动压缩包,下载地址:https://github.com/atjiu/pybbs/releases 20 | - 解压, 修改解压出来的文件夹里的 `application-prod.yml` 文件,只需要修改一个地方,就是数据库的连接信息,[配置方法](zh-cn/base) 21 | - 运行压缩包里的脚本 `sh start.sh` 22 | - 关闭服务运行 `sh shutdown.sh` 23 | - 查看启动日志 `tail -200f log.file` 24 | - 查看服务是否启动 `ps -ef|grep pybbs` 如果有pybbs的进程,就说明服务启动了 25 | - 浏览器运行 `http://localhost:8080` , 后台地址 `http://localhost:8080/adminlogin` 后台用户名 admin 密码 123123 26 | - 网站的其它配置,参见文档 27 | 28 | === docker运行 29 | 30 | - 保证服务器有docker和docker-compose环境 31 | - `git clone https://github.com/atjiu/pybbs` 或 下载最新版 32 | - cd pybbs进入项目 33 | - 运行 `docker-compose up -d` 命令启动容器,-d是后台运行的意思 34 | - 浏览器运行 `http://localhost:8080` , 后台地址 `http://localhost:8080/adminlogin` 后台用户名 admin 密码 123123 35 | - 关闭容器 `docker-compose down` 36 | - 查看日志 `docker-compose logs -f server` 37 | 38 | *第一次运行会比较慢,视服务器性能和网速决定* 39 | 40 | *项目根目录下会生成 `mysql` 文件夹为数据库文件,注意谨慎操作,另外论坛启动后,用户上传的图片和系统生成的默认头像会自动同步到根目录下的 `static` 文件夹下* 41 | 42 | *这个Dockerfile是 https://github.com/zzzzbw[@zzzzbw] 大佬帮忙开发的 万分感谢!!* 43 | 44 | [TIP] 45 | *自己实在部署不好,朋也也可以代劳,不过是有偿的哦* 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/20210308113951.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/20210308113951.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/20230310.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/20230310.jpg -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/20230310104837.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/20230310104837.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/7C56195B1FE6F942649D30D65416EE80.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/7C56195B1FE6F942649D30D65416EE80.jpg -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190103-135005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190103-135005.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190103-135046.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190103-135046.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190103-154507.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190103-154507.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190103-154553.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190103-154553.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190103-155656.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190103-155656.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190107-135811.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190107-135811.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190107-135903.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190107-135903.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190107-140155.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190107-140155.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190109-111822.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190109-111822.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190123-103144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190123-103144.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190131-173707.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190131-173707.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190418-153321.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190418-153321.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/QQ20190725-111655.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/QQ20190725-111655.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/TIM20190417185246.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/TIM20190417185246.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/TIM20190417185315.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/TIM20190417185315.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/TIM20190417185600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/TIM20190417185600.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/captcha.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/images/captcha.gif -------------------------------------------------------------------------------- /docs/src/main/asciidoc/index.asciidoc: -------------------------------------------------------------------------------- 1 | = 朋也社区文档 2 | 3 | :Author: 朋也 4 | :Email: 5 | :Date: 2019 6 | :Revision: 1.0 7 | :revdate: {docdate} 8 | :toclevels: 3 9 | :sectnumlevels: 5 10 | 11 | == 简介 12 | 13 | include::introduction.asciidoc[leveloffset=+1] 14 | 15 | == 快速开始 16 | 17 | include::ready.asciidoc[] 18 | include::getting-started.asciidoc[] 19 | 20 | == 朋也社区案例 21 | 22 | include::case.asciidoc[] 23 | 24 | == 系统配置 25 | 26 | include::base-config.asciidoc[] 27 | include::email-config.asciidoc[] 28 | include::es-config.asciidoc[] 29 | include::redis-config.asciidoc[] 30 | include::upload-config.asciidoc[] 31 | include::oauth-config.asciidoc[] 32 | include::sms-config.asciidoc[] 33 | include::ws-config.asciidoc[] 34 | include::telegram-config.asciidoc[tags=main] 35 | include::theme.asciidoc[] 36 | 37 | == 公网部署 38 | 39 | include::deploy.asciidoc[leveloffset=+1] 40 | 41 | == 主题开发 42 | 43 | include::theme-dev.asciidoc[leveloffset=+1] 44 | 45 | == 接口文档 46 | 47 | include::api-gettoken.asciidoc[] 48 | include::api-request-demo.asciidoc[] 49 | include::api-return-object.asciidoc[] 50 | include::api-doc.asciidoc[] 51 | 52 | == Q&A 53 | 54 | include::qa.asciidoc[] 55 | 56 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/introduction.asciidoc: -------------------------------------------------------------------------------- 1 | 为高度定制化而生的论坛, 内置了异常强大的功能, 使用主流Java web开发框架(SpringBoot)开发, 更方便你进行二次开发, 还在犹豫什么, 用就是了 2 | 3 | == 为什么使用它? 4 | 5 | 1. 异常详细的文档 6 | 2. 丰富多彩的主题 7 | 3. 随心所欲的开启/关闭各种集成的服务 8 | 4. 傻瓜式部署,让不懂开发的你也能搭建自己的论坛 9 | 5. 强大的API接口,为学习编程的你提供服务端环境 10 | 6. 更多功能,期待你去发现 11 | 12 | == 开源协议 13 | 14 | 开源协议是 `GNU AGPLv3` 15 | 16 | *完全免费* 源代码毫不保留的全部开源,怕留后门的,可以自行查看,不过要遵守开源协议哦 17 | 18 | == 使用过程中碰到问题怎么办? 19 | 20 | 1. 一定要仔细看文档,基本上能碰到的问题在文档中都能找到解决办法 21 | 2. 往下翻, 仔细查看Q&A 22 | 3. 去Github Issue上提问 23 | 4. QQ群 1048094312 https://jq.qq.com/?_wv=1027&k=nGLY4QmH[点击链接加入群聊【朋也社区 - 问与答】] 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/oauth-config.asciidoc: -------------------------------------------------------------------------------- 1 | === Github登录配置方法 2 | 3 | 申请clientId, clientSecret地址:https://github.com/settings/developers 前提要先登录github 4 | 5 | 打开页面后,点击 `New OAuth APP` 按钮 6 | 7 | image:./images/QQ20190107-135811.png[] 8 | 9 | 填上必要的信息 10 | 11 | image:./images/QQ20190107-140155.png[] 12 | 13 | 填写好之后,保存,跳转的页面上就有 clientId, clientSecret信息了,如下图 14 | 15 | image:./images/QQ20190107-135903.png[] 16 | 17 | 拷贝上图中红框内容,粘贴到网站后台系统设置页面里的 Github 配置信息里 18 | 19 | 刷新登录页面即可出现相应按钮 20 | 21 | image:./images/QQ20190418-153321.png[] 22 | 23 | *注意* 24 | 25 | - 网站域名必须外网能访问,如是你要在内网测试,可以使用ngrok,frp等工具来做内网穿透,具体使用方法百度吧,网上很多 26 | - 回调地址格式是 网站域名+/oauth/github/callback 假如你的域名是 `http://example.com` 那么这里的回调地址就是 `http://example.com/oauth/github/callback` 不要填错了 27 | 28 | 配置好之后,保存,再次回到首页,就可以看到页面 header 上就有了`Github登录`的入口了 29 | 30 | === 微信登录配置方法 31 | 32 | 注册开放平台 http://open.weixin.qq.com 然后创建web应用,跟着步骤一步一步来就可以了,最后可以拿到 `appid` `appsecret` 加上在创建应用的时候填上的 `callback` 33 | 34 | 总共三个参数,都配置在`朋也社区`后台设置页面里`微信登录`区域,然后刷新登录页面,就有微信登录按钮了!! 35 | 36 | image:./images/QQ20190418-153321.png[] 37 | 38 | *感谢 https://github.com/gdhua[@gdhua] 提供的微信联合登录要用到的 `appid` `appsecret`* 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/ready.asciidoc: -------------------------------------------------------------------------------- 1 | === 准备环境[必须] 2 | 3 | 1. Java环境,项目是用java开发的,java运行环境是必须的,版本:jdk8 4 | 2. MySQL数据库服务,项目采用MySQL存储数据,所以MySQL是必须的,版本5.7.x 5 | 6 | === 准备环境[非必须] 7 | 8 | 1. redis 9 | 2. elasticsearch 10 | 11 | 这两个服务在下面配置中有介绍 12 | 13 | 就这两样,就可以搭建一个自己的论坛了,当然这只限于局域网内使用,如果想搭建上线,请查看下面的配置方法 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/redis-config.asciidoc: -------------------------------------------------------------------------------- 1 | === Redis配置 2 | 3 | 程序也内置了redis做缓存的功能 4 | 5 | 配置地址:启动程序 -> 登录后台 -> 系统设置 6 | 7 | 根据自己的环境配置上redis的信息,保存就可以用了,不用重启哦!! 8 | 9 | image:./images/QQ20190103-154553.png[] 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/sms-config.asciidoc: -------------------------------------------------------------------------------- 1 | === 短信验证码登录/注册 2 | 3 | 在群友提供的阿里云短信的key, secret及模板的帮助下,朋也社区也有了短信验证码登录的功能了,配置如下 4 | 5 | 登录 http://aliyun.com ,打开 https://dysms.console.aliyun.com/dysms.htm?spm=5176.8195934.1283918..623230c9cuQEpk#/domestic/text/sign 链接 6 | 7 | 配置 `签名` `模板` `区域` 8 | 9 | 然后在 https://usercenter.console.aliyun.com/?spm=5176.12207334.0.0.8dae1cbeSq0lnd#/manage/ak 这个页面拿到 `key` `secret` 10 | 11 | 最后配置在项目后台的系统设置里,刷新登录页面,就会有手机号登录的按钮出现 12 | 13 | image:./images/QQ20190418-153321.png[] 14 | 15 | [WARNING] 16 | ==== 17 | 短信服务目前只支持阿里云短信服务 18 | 19 | 申请模板的时候,动态内容的变量名是 `${code}` 请不要写成其它的 20 | ==== 21 | 22 | *感谢 https://github.com/sunkaifei[@sunkaifei] 提供的阿里云短信验证码登录要用到的 `key` `secret`* 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/telegram-config.asciidoc: -------------------------------------------------------------------------------- 1 | // tag::main[] 2 | 3 | === Telegram配置 4 | 5 | 这个功能目前主要是为了方便站长管理论坛的,接入tg后,当论坛内有新的评论会第一时间推送到tg上,站长可在tg上进行审核与删除的操作 6 | 7 | --- 8 | 9 | 因为tg在国内是被墙的状态,但它的api确实好用,所以就需要额外加一个配置项**代理** 10 | 11 | **代理怎么配置?**这个会的就不多说了,不会的就跳过这个功能吧! 12 | 13 | **tg那几项怎么配置?**参见这篇博客 https://atjiu.github.io/2023/03/09/telegram-bot-api/ 14 | 15 | image:images/20230310104837.png[] 16 | 17 | 配置好之后的使用效果如下视频(*如果在网页上看不到画面,可将其下载到本地打开观看*) 18 | 19 | ++++ 20 | 24 | ++++ 25 | 26 | // end::main[] 27 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/theme.asciidoc: -------------------------------------------------------------------------------- 1 | === 内置主题 2 | 3 | - default: 默认主题(使用Bootstrap3开发的) 4 | - simple: 黑白简洁主题(仿hacker news开发的, 有时候看着也挺像kindle风格的) 5 | 6 | === 更换主题 7 | 8 | 启动后,登录后台,可进行选择使用哪套,选择后保存,立即生效 9 | 10 | image:./images/QQ20190131-173707.png[] 11 | 12 | 想体验的,可以在体验环境上修改查看 13 | 14 | 关于主题怎么开发,下一篇介绍 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/upload-config.asciidoc: -------------------------------------------------------------------------------- 1 | === 上传配置 2 | 3 | 程序启动后,要配置上传文件保存路径,否则用户注册会看不到自己的头像 4 | 5 | 配置地址:启动程序 -> 登录后台 -> 系统设置 6 | 7 | image:./images/QQ20190725-111655.png[] 8 | 9 | [WARNING] 10 | ==== 11 | *路径一定要是绝对路径* 12 | 13 | 看到这还配置不好?请查看 https://atjiu.github.io/pybbs/#_%E9%9D%9Enginx%E9%9D%99%E6%80%81%E6%98%A0%E5%B0%84%E9%85%8D%E7%BD%AE[非nginx静态映射配置] 14 | ==== 15 | 16 | === 非nginx静态映射配置 17 | 18 | 如果你没有使用nginx做静态文件映射,就请配置在程序启动目录下,举个例子: 19 | 20 | 你下载的jar包存放在 `/opt/pybbs/pybbs.jar` 那么这里的地址就应该是 `/opt/pybbs/static/upload/` 21 | 22 | 如果你用的是docker部署的服务,那这个路径配置就是固定的 `/app/static` 了,上传的图片会自动同步到docker启动目录下的static文件夹里 23 | 24 | 举例: 25 | 26 | idea启动服务配置如下 27 | 28 | - windows: 假如项目文件夹在 D:/pybbs。那么静态文件访问地址不要变,上传文件路径修改为 D:/pybbs/static/upload/ 29 | - macos/linux: 假如项目文件夹在 /Users/xxx/pybbs (其中xxx是你的系统用户名) 那么静态文件访问地址不要变,上传文件路径修改为 /Users/xxx/pybbs/static/upload/ 30 | 31 | jar包启动配置如下 32 | 33 | - windows: 假如项目文件夹在 D:/pybbs jar包在 D:/pybbs/pybbs.jar 那么静态文件访问地址不要变,上传文件路径修改为 D:/pybbs/static/upload/ 34 | - macos/linux: 假如项目文件夹在 /Users/xxx/pybbs (其中xxx是你的系统用户名),jar包在 /Users/xxx/pybbs/pybbs.jar 那么静态文件访问地址不要变,上传文件路径修改为 /Users/xxx/pybbs/static/upload/ 35 | 36 | **如果你是idea启动的话,上传完图片后,看不到图片的话,重启一下服务。** 37 | 38 | [WARNING] 39 | **如果看到这还不会配置,请不要使用pybbs了,它不适合你,谢谢!** 40 | 41 | === nginx静态文件映射配置方法 42 | 43 | nginx静态文件映射配置 44 | 45 | [source,indent=0] 46 | ---- 47 | server { 48 | #... 49 | location /static/ { 50 | root /opt/cdn/; 51 | autoindex on; 52 | } 53 | } 54 | ---- 55 | 56 | 那么你这个地上的配置就应该是 `/opt/cdn/static/upload/` 57 | 58 | === 静态文件访问地址 59 | 60 | 默认给的是 `http://localhost:8080/static/upload/` 如果你的访问域名是 `http://example.com` 那这里就要换成 `http://example.com/static/upload/` 61 | 62 | 63 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/videos/1678413054469.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/docs/src/main/asciidoc/videos/1678413054469.mp4 -------------------------------------------------------------------------------- /docs/src/main/asciidoc/ws-config.asciidoc: -------------------------------------------------------------------------------- 1 | === 开启WebSocket 2 | 3 | 重新更新了一下websocket的实现,换成了javax.websocket包下的类实现,没有了之间的关闭服务还要等会的问题了 4 | 5 | 而且页面上也不用再多引入一个socket.io的js了,纯原生的js实现 6 | 7 | [TIP] 8 | ==== 9 | 注意,因为网站是服务端渲染的,所以每次请求页面都会刷新,websocket也会重连,这就有点蛋疼了 10 | 11 | 不过也不是没办法, 可以给网站加上pjax支持,也可以将这个功能用在纯js渲染的网站上,很显然这两个功能pybbs都还没有 12 | ==== 13 | 14 | 目前围绕ws开发的功能有如下 15 | 16 | - 自己的话题被收藏了,会收到通知 17 | - 自己的话题被评论了,会收到通知 18 | - 自己的评论被回复了,会收到通知 19 | - 进入页面后,ws会自动获取未读消息数,然后展示在页面上的Header和`document.title`上 20 | 21 | 效果如下 22 | 23 | image:./images/7C56195B1FE6F942649D30D65416EE80.jpg[] 24 | 25 | 当然开启websocket服务也不是没有好处的,比如: 26 | 27 | - 上图中别人回复了自己的评论就会立即收到消息 28 | - 发一个帖子等着别人回复,不用一直刷新页面看有没有新消息了 29 | 30 | --- 31 | 32 | 如果你看了上面的说明后,还是想开启,配置如下 33 | 34 | image:./images/QQ20190123-103144.png[] 35 | 36 | 只有两个配置 37 | 38 | 1. 开启功能,不多说 39 | 2. ws连接地址,协议是 `ws` 或者 `wss`(如果你网站访问用的是https,那这个就应该是wss),其它跟上面配置的网站访问域名一样 40 | 41 | 然后直接启动系统即可,注意,ws服务在前端只有登录后的用户才有效 42 | 43 | 44 | -------------------------------------------------------------------------------- /fronts/readme.txt: -------------------------------------------------------------------------------- 1 | 欢迎各位前端大佬使用pybbs提供的api开发前端页面 2 | 3 | 位置就在这个目录下,创建好前端项目的名字,请不要跟别人一样 4 | 5 | 注意:开发好的前端项目一定要写好怎么打包部署,文档就写在前端项目的根目录下的README.md里即可 6 | 7 | 例:我现在想开发一个前端项目,取名为 pybbs-react-front 8 | 9 | 目录结构如下:pybbs/fronts/pybbs-react-front 10 | 11 | README.md位置:pybbs/fronts/pybbs-react-front/README.md 12 | 13 | 谢谢! -------------------------------------------------------------------------------- /plugins/comment-layer-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | co.yiiu.pybbs 8 | comment-layer-plugin 9 | 1.0 10 | 11 | 12 | 13 | co.yiiu 14 | pybbs 15 | 5.2.1 16 | provided 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | org.apache.maven.plugins 25 | maven-compiler-plugin 26 | 3.8.1 27 | 28 | 1.8 29 | 1.8 30 | UTF-8 31 | true 32 | 33 | 34 | 35 | 36 | org.apache.maven.plugins 37 | maven-surefire-plugin 38 | 39 | true 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /plugins/redis-cache-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | co.yiiu.pybbs 8 | redis-cache-plugin 9 | 1.0 10 | 11 | 12 | 13 | co.yiiu 14 | pybbs 15 | 5.2.1 16 | provided 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | org.apache.maven.plugins 25 | maven-compiler-plugin 26 | 3.8.1 27 | 28 | 1.8 29 | 1.8 30 | UTF-8 31 | true 32 | 33 | 34 | 35 | 36 | org.apache.maven.plugins 37 | maven-surefire-plugin 38 | 39 | true 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | co.yiiu.pybbs 8 | theme-simple-plugin 9 | 1.0 10 | 11 | 12 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/comment/edit.ftl: -------------------------------------------------------------------------------- 1 | <#include "../layout/layout.ftl"/> 2 | <@html page_title="编辑话题" page_tab=""> 3 |
4 |
5 | 主页 / ${topic.title} / 编辑评论 6 | 上传图片 7 |
8 |
9 | 10 |
11 |
12 | 13 |
14 |
15 | 47 | <#include "../components/upload.ftl"/> 48 | 49 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/components/loading.ftl: -------------------------------------------------------------------------------- 1 | 53 |
54 |
55 |
56 |
57 |
58 |
59 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/components/notification.ftl: -------------------------------------------------------------------------------- 1 | <#macro notification userId read limit> 2 | <@tag_notifications userId=userId read=read limit=limit> 3 | <#list notifications as notification> 4 |
5 | 6 | ${notification.username} 7 | ${model.formatDate(notification.inTime)} 8 | <#if notification.action == "COMMENT"> 9 | 评论了你的话题 ${notification.title} 10 | <#elseif notification.action == "REPLY"> 11 | 在话题 ${notification.title} 下回复了你 12 | <#elseif notification.action == "COLLECT"> 13 | 收藏了你的话题 ${notification.title} 14 | 15 |
16 |
${model.formatContent(notification.content)}
17 | 18 | <#if notifications?size == 0> 19 | 无 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/components/topics.ftl: -------------------------------------------------------------------------------- 1 | <#macro topics page> 2 | 3 | <#list page.records as topic> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 21 | 22 | 23 | 24 | 25 | 26 |
${topic.title}
11 | <#if topic.top> 12 | 置顶 13 | <#elseif topic.good == true> 14 | 精华 15 | 16 | ${topic.view} 次点击  17 | ${topic.username}  18 | 发布于 ${model.formatDate(topic.inTime)} | 19 | ${topic.commentCount} 评论  20 |
style="border: 0;">
27 | 28 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/components/user_collects.ftl: -------------------------------------------------------------------------------- 1 | <#macro user_collects username pageNo pageSize paginate=false> 2 | <@tag_user_collects username=username pageNo=pageNo pageSize=pageSize> 3 | 8 | <#if paginate && collects.current < collects.pages> 9 | 查看更多 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/components/user_comments.ftl: -------------------------------------------------------------------------------- 1 | <#macro user_comments username pageNo pageSize paginate=false> 2 | <@tag_user_comments username=username pageNo=pageNo pageSize=pageSize> 3 | 19 | <#if paginate && comments.current < comments.pages> 20 | 查看更多 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/components/user_topics.ftl: -------------------------------------------------------------------------------- 1 | <#macro user_topics username pageNo pageSize paginate=false> 2 | <@tag_user_topics username=username pageNo=pageNo pageSize=pageSize> 3 | 8 | <#if paginate && topics.current < topics.pages> 9 | 查看更多 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/error.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <#-- 6 | <#--content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">--> 7 | 8 | Ooooops, 出错了~~ 9 | 10 | <#--css--> 11 | 12 | 13 | 14 |
15 |
16 |

: (

17 |

${errorCode}

18 |

${exception.message!}

19 |
20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/index.ftl: -------------------------------------------------------------------------------- 1 | <#include "layout/layout.ftl"/> 2 | <@html page_title="首页" page_tab=tab> 3 | <#if active?? && active> 4 |
激活成功
5 | 6 | <@tag_topics pageNo=pageNo tab=tab> 7 | <#include "components/topics.ftl"/> 8 | <@topics page=page/> 9 | 10 | <#if page.current < page.pages> 11 | 查看更多 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/layout/footer.ftl: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/layout/layout.ftl: -------------------------------------------------------------------------------- 1 | <#macro html page_title page_tab> 2 | 3 | 4 | 5 | 6 | <#-- 7 | <#--content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">--> 8 | 9 | ${page_title!} - ${site.name} 10 | 11 | <#--css--> 12 | 13 | 14 | <#--javascript--> 15 | 16 | 17 | 18 | <#include "../components/loading.ftl"/> 19 |
20 | <#include "header.ftl"/> 21 | <@header page_tab=page_tab/> 22 |
<#nested />
23 | <#include "footer.ftl"/> 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/notifications.ftl: -------------------------------------------------------------------------------- 1 | <#include "layout/layout.ftl"/> 2 | <@html page_title="通知" page_tab="notification"> 3 |
4 | 5 | 新消息 6 | 7 | 8 |
9 | <#include "components/notification.ftl"/> 10 | <@notification userId=_user.id read=0 limit=-1/> 11 |
12 |
13 | 已读消息 14 |
15 | <#include "components/notification.ftl"/> 16 | <@notification userId=_user.id read=1 limit=20/> 17 |
18 | 44 | 45 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/search.ftl: -------------------------------------------------------------------------------- 1 | <#include "layout/layout.ftl"/> 2 | <@html page_title="搜索" page_tab=""> 3 | 搜索 ${keyword!} 结果 4 | <@tag_search pageNo=pageNo keyword=keyword> 5 | 6 | <#list page.records as map> 7 | 8 | 9 | 10 | 11 |
${map.title!}
12 | <#if page.current < page.pages> 13 | 查看更多 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/tag/tag.ftl: -------------------------------------------------------------------------------- 1 | <#include "../layout/layout.ftl"/> 2 | <@html page_title="标签" page_tab=""> 3 | 4 | 5 | 15 | 16 | <#if tag.description??> 17 | 18 | 19 | 20 | 21 |
6 | <#if tag.icon??> 7 |   8 | 9 | ${tag.name}  10 | 共有 ${tag.topicCount!0} 篇话题 11 | <#if _user??> 12 | 发布话题 13 | 14 |
${tag.description}
22 |
23 | <#include "../components/topics.ftl"/> 24 | <@topics page=page/> 25 | 26 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/tag/tags.ftl: -------------------------------------------------------------------------------- 1 | <#include "../layout/layout.ftl"/> 2 | <@html page_title="标签" page_tab="tags"> 3 |
4 | <@tag_tags pageNo=pageNo pageSize=40> 5 | <#list page.records as tag> 6 |
7 | <#if tag.icon??> 8 | 9 | 10 | ${tag.name} X ${tag.topicCount!0} 11 | <#if tag.description??> 12 |
${tag.description}
13 | 14 |
15 | <#if (tag_index + 1) % 4 == 0> 16 |
17 | 18 | 19 |
20 | <#if page.current < page.pages> 21 | 查看更多 22 | 23 | 24 |
25 | 26 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/top100.ftl: -------------------------------------------------------------------------------- 1 | <#include "layout/layout.ftl"/> 2 | <@html page_title="Top100" page_tab=""> 3 | Top100 积分排行 4 | <@tag_score limit=100> 5 | 6 | <#list users as user> 7 | 8 | 11 | 12 | 13 | 14 | 15 |
9 | 10 | ${user.username}${user.score!0}
16 | 17 | 18 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/user/collects.ftl: -------------------------------------------------------------------------------- 1 | <#include "../layout/layout.ftl"/> 2 | <@html page_title="${username}收藏的话题" page_tab=""> 3 | 用户 ${username} / 收藏的话题 4 | <#include "../components/user_collects.ftl"/> 5 | <@user_collects username=username pageNo=pageNo pageSize=site.page_size paginate=true/> 6 | 7 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/user/comments.ftl: -------------------------------------------------------------------------------- 1 | <#include "../layout/layout.ftl"/> 2 | <@html page_title="${username}评论的话题" page_tab=""> 3 | 用户 ${username} / 评论的话题 4 | <#include "../components/user_comments.ftl"/> 5 | <@user_comments username=username pageNo=pageNo pageSize=site.page_size paginate=true/> 6 | 7 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/user/profile.ftl: -------------------------------------------------------------------------------- 1 | <#include "../layout/layout.ftl"/> 2 | <@html page_title=username + " 的个人主页" page_tab="user"> 3 |
4 |
5 | 6 |
7 |
${user.username}
8 |
    9 |
  • 积分: ${user.score!0}
  • 10 |
  • 收藏话题: ${collectCount!0}
  • 11 |
  • 入驻时间: ${model.formatDate(user.inTime)}
  • 12 | <#if user.email?? && user.email != ""> 13 |
  • ${user.email}
  • 14 | 15 | <#if user.website?? && user.website != ""> 16 |
  • ${user.website}
  • 17 | 18 | <#if user.githubName?? && user.githubName != ""> 19 |
  • Github: ${githubLogin}
  • 20 | 21 | <#if user.bio?? && user.bio != ""> 22 |
  • ${user.bio}
  • 23 | 24 |
25 |
26 |
27 |
28 |
29 | ${username} 近期的话题 30 | 查看更多 31 |
32 | <#include "../components/user_topics.ftl"/> 33 | <@user_topics username=username pageNo=1 pageSize=10/> 34 |
35 |
36 | ${username} 参与的评论 37 | 查看更多 38 |
39 | <#include "../components/user_comments.ftl"/> 40 | <@user_comments username=username pageNo=1 pageSize=10/> 41 |
42 |
43 |
44 | 45 | -------------------------------------------------------------------------------- /plugins/theme-simple-plugin/src/main/resources/templates/theme/simple/user/topics.ftl: -------------------------------------------------------------------------------- 1 | <#include "../layout/layout.ftl"/> 2 | <@html page_title="${username}创建的话题" page_tab=""> 3 | 用户 ${username} / 创建的话题 4 | <#include "../components/user_topics.ftl"/> 5 | <@user_topics username=username pageNo=pageNo pageSize=site.page_size paginate=true/> 6 | 7 | -------------------------------------------------------------------------------- /shutdown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | ps -ef | grep pybbs.jar | grep -v grep | cut -c 9-15 | xargs kill 3 | -------------------------------------------------------------------------------- /snapshot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/snapshot/1.png -------------------------------------------------------------------------------- /snapshot/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/snapshot/2.png -------------------------------------------------------------------------------- /snapshot/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/snapshot/3.png -------------------------------------------------------------------------------- /snapshot/alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/snapshot/alipay.png -------------------------------------------------------------------------------- /snapshot/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/snapshot/wechat.png -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/PybbsApplication.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; 6 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 7 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 8 | 9 | // 不用默认配置的数据源,自己配置 10 | @SpringBootApplication(scanBasePackages = "co.yiiu.pybbs", 11 | exclude = {DataSourceAutoConfiguration.class, FlywayAutoConfiguration.class}) 12 | @EnableAspectJAutoProxy 13 | public class PybbsApplication { 14 | public static void main(String[] args) { 15 | SpringApplication.run(PybbsApplication.class, args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/DataSourceHelper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.config; 2 | 3 | import co.yiiu.pybbs.util.SpringContextUtil; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import javax.annotation.PostConstruct; 7 | import javax.annotation.Resource; 8 | import java.net.URI; 9 | import java.net.URISyntaxException; 10 | import java.sql.Connection; 11 | import java.sql.DriverManager; 12 | import java.sql.SQLException; 13 | import java.sql.Statement; 14 | 15 | /** 16 | * Created by tomoya. 17 | * Copyright (c) 2018, All Rights Reserved. 18 | * https://atjiu.github.io 19 | */ 20 | @Configuration 21 | public class DataSourceHelper { 22 | 23 | @Resource 24 | private SiteConfig siteConfig; 25 | 26 | @PostConstruct 27 | public void init() { 28 | if (siteConfig == null) siteConfig = SpringContextUtil.getBean(SiteConfig.class); 29 | try { 30 | Class.forName(siteConfig.getDatasource_driver()); 31 | URI uri = new URI(siteConfig.getDatasource_url().replace("jdbc:", "")); 32 | String host = uri.getHost(); 33 | int port = uri.getPort(); 34 | String path = uri.getPath(); 35 | String query = uri.getQuery(); 36 | Connection connection = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "?" + query, 37 | siteConfig.getDatasource_username(), siteConfig.getDatasource_password()); 38 | Statement statement = connection.createStatement(); 39 | statement.executeUpdate("CREATE DATABASE IF NOT EXISTS `" + path.replace("/", "") + "` DEFAULT CHARACTER SET = " 40 | + "" + "`utf8` COLLATE `utf8_general_ci`;"); 41 | statement.close(); 42 | connection.close(); 43 | } catch (URISyntaxException | ClassNotFoundException | SQLException e) { 44 | e.printStackTrace(); 45 | System.exit(0); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/FlywayConfig.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.config; 2 | 3 | import org.flywaydb.core.Flyway; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.DependsOn; 6 | 7 | import javax.annotation.PostConstruct; 8 | import javax.annotation.Resource; 9 | import javax.sql.DataSource; 10 | 11 | /** 12 | * Created by tomoya. 13 | * Copyright (c) 2018, All Rights Reserved. 14 | * https://atjiu.github.io 15 | */ 16 | @Configuration 17 | public class FlywayConfig { 18 | 19 | @Resource 20 | private DataSource dataSource; 21 | 22 | @PostConstruct 23 | @DependsOn("dataSourceHelper") 24 | public void migrate() { 25 | Flyway flyway = Flyway.configure().dataSource(dataSource).locations("classpath:db/migration", 26 | "filesystem:db/migration").baselineOnMigrate(true).load(); 27 | flyway.migrate(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/GlobalCorsConfiguration.java: -------------------------------------------------------------------------------- 1 | //package co.yiiu.pybbs.config; 2 | // 3 | //import org.springframework.context.annotation.Bean; 4 | //import org.springframework.context.annotation.Configuration; 5 | //import org.springframework.web.cors.CorsConfiguration; 6 | //import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 7 | //import org.springframework.web.filter.CorsFilter; 8 | // 9 | ///** 10 | // * 如果出现跨域问题,把这个类注释打开 11 | // *

12 | // * 正常开发原生app是没有跨域问题的,当开发前端项目时就会有了,这时候就需要这个类来解决跨域问题了 13 | // */ 14 | //@Configuration 15 | //public class GlobalCorsConfiguration { 16 | // 17 | // @Bean 18 | // public CorsFilter corsFilter() { 19 | // CorsConfiguration corsConfiguration = new CorsConfiguration(); 20 | // corsConfiguration.setAllowCredentials(true); 21 | // corsConfiguration.addAllowedOrigin("*"); 22 | // corsConfiguration.addAllowedHeader("*"); 23 | // corsConfiguration.addAllowedMethod("*"); 24 | //// corsConfiguration.addExposedHeader("head1"); 25 | // //corsConfiguration.addExposedHeader("Location"); 26 | // UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource(); 27 | // urlBasedCorsConfigurationSource.registerCorsConfiguration("/api/**", corsConfiguration); 28 | // return new CorsFilter(urlBasedCorsConfigurationSource); 29 | // } 30 | //} -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.config; 2 | 3 | import co.yiiu.pybbs.service.ISystemConfigService; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.DependsOn; 6 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.util.StringUtils; 9 | import org.springframework.web.client.RestTemplate; 10 | 11 | import javax.annotation.PostConstruct; 12 | import javax.annotation.Resource; 13 | import java.net.InetSocketAddress; 14 | import java.net.Proxy; 15 | 16 | @Component 17 | @DependsOn("mybatisPlusConfig") 18 | public class RestTemplateConfig { 19 | 20 | private String proxyHost; 21 | private Integer proxyPort; 22 | 23 | @Resource 24 | private ISystemConfigService systemConfigService; 25 | 26 | @PostConstruct 27 | public void init() { 28 | proxyHost = systemConfigService.selectAllConfig().get("http_proxy"); 29 | String http_proxy_port = systemConfigService.selectAllConfig().get("http_proxy_port"); 30 | if (!StringUtils.isEmpty(http_proxy_port)) proxyPort = Integer.parseInt(http_proxy_port); 31 | } 32 | 33 | @Bean 34 | public RestTemplate restTemplate() { 35 | SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); 36 | 37 | if (!StringUtils.isEmpty(proxyHost) && proxyPort != null) { 38 | Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); 39 | requestFactory.setProxy(proxy); 40 | } 41 | 42 | return new RestTemplate(requestFactory); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/ServerRunner.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.config; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2018, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | @Component 14 | public class ServerRunner implements CommandLineRunner { 15 | 16 | private Logger log = LoggerFactory.getLogger(ServerRunner.class); 17 | 18 | @Override 19 | public void run(String... args) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/ShiroTag.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.config; 2 | 3 | import org.apache.shiro.SecurityUtils; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.util.StringUtils; 6 | 7 | /** 8 | * Created by tomoya. 9 | * Copyright (c) 2018, All Rights Reserved. 10 | * https://atjiu.github.io 11 | */ 12 | @Component 13 | public class ShiroTag { 14 | 15 | // 判断当前用户是否已经登录认证过 16 | public boolean isAuthenticated() { 17 | return SecurityUtils.getSubject().isAuthenticated(); 18 | } 19 | 20 | // 获取当前用户的用户名 21 | public String getPrincipal() { 22 | return (String) SecurityUtils.getSubject().getPrincipal(); 23 | } 24 | 25 | // 判断用户是否有 xx 角色 26 | public boolean hasRole(String name) { 27 | return SecurityUtils.getSubject().hasRole(name); 28 | } 29 | 30 | // 判断用户是否有 xx 权限 31 | public boolean hasPermission(String name) { 32 | return !StringUtils.isEmpty(name) && SecurityUtils.getSubject().isPermitted(name); 33 | } 34 | 35 | // 判断用户是否有 xx 权限 36 | public boolean hasPermissionOr(String... name) { 37 | boolean[] permitted = SecurityUtils.getSubject().isPermitted(name); 38 | for (boolean b : permitted) { 39 | // 如果有一个权限,就成功 40 | if (b) { 41 | return true; 42 | } 43 | } 44 | return false; 45 | } 46 | 47 | // 判断用户是否有 xx 权限 48 | public boolean hasPermissionAnd(String... name) { 49 | boolean[] permitted = SecurityUtils.getSubject().isPermitted(name); 50 | for (boolean b : permitted) { 51 | // 必须所有的权限都有,才成功 52 | if (!b) { 53 | return false; 54 | } 55 | } 56 | return true; 57 | } 58 | 59 | // 判断用户是否有 xx 权限 60 | public boolean hasAllPermission(String... name) { 61 | return SecurityUtils.getSubject().isPermittedAll(name); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/SiteConfig.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.config; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2018, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | @Configuration 14 | @ConfigurationProperties(value = "site") 15 | public class SiteConfig implements Serializable { 16 | 17 | private static final long serialVersionUID = -7632268193700036274L; 18 | 19 | private String datasource_driver; 20 | private String datasource_url; 21 | private String datasource_username; 22 | private String datasource_password; 23 | 24 | public String getDatasource_driver() { 25 | return datasource_driver; 26 | } 27 | 28 | public void setDatasource_driver(String datasource_driver) { 29 | this.datasource_driver = datasource_driver; 30 | } 31 | 32 | public String getDatasource_url() { 33 | return datasource_url; 34 | } 35 | 36 | public void setDatasource_url(String datasource_url) { 37 | this.datasource_url = datasource_url; 38 | } 39 | 40 | public String getDatasource_username() { 41 | return datasource_username; 42 | } 43 | 44 | public void setDatasource_username(String datasource_username) { 45 | this.datasource_username = datasource_username; 46 | } 47 | 48 | public String getDatasource_password() { 49 | return datasource_password; 50 | } 51 | 52 | public void setDatasource_password(String datasource_password) { 53 | this.datasource_password = datasource_password; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/realm/MyCredentialsMatcher.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.config.realm; 2 | 3 | import co.yiiu.pybbs.util.bcrypt.BCryptPasswordEncoder; 4 | import org.apache.shiro.authc.AuthenticationInfo; 5 | import org.apache.shiro.authc.AuthenticationToken; 6 | import org.apache.shiro.authc.credential.CredentialsMatcher; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2018, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | public class MyCredentialsMatcher implements CredentialsMatcher { 14 | 15 | @Override 16 | public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { 17 | // 大坑!!!!!!!!!!!!!!!!!!! 18 | // 明明token跟info两个对象的里的Credentials类型都是Object,断点看到的类型都是 char[] 19 | // 但是!!!!! token里转成String要先强转成 char[] 20 | // 而info里取Credentials就可以直接使用 String.valueOf() 转成String 21 | // 醉了。。 22 | String rawPassword = String.valueOf((char[]) token.getCredentials()); 23 | String encodedPassword = String.valueOf(info.getCredentials()); 24 | return new BCryptPasswordEncoder().matches(rawPassword, encodedPassword); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/realm/MyShiroFilter.java: -------------------------------------------------------------------------------- 1 | //package co.yiiu.pybbs.config.realm; 2 | // 3 | //import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; 4 | // 5 | //import javax.servlet.ServletRequest; 6 | //import javax.servlet.ServletResponse; 7 | //import javax.servlet.http.HttpServletRequest; 8 | //import javax.servlet.http.HttpServletResponse; 9 | //import javax.servlet.http.HttpSession; 10 | 11 | /** 12 | * 不使用自定义的filter处理登录逻辑了 13 | * 换回Controller处理 14 | * 好处:shiro的错误信息可以捕获到,然后进行一些处理再返回给用户,避免出现一些莫名其妙的问题 15 | */ 16 | //public class MyShiroFilter extends FormAuthenticationFilter { 17 | // @Override 18 | // protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { 19 | // String code = request.getParameter("code"); 20 | // HttpSession session = ((HttpServletRequest) request).getSession(); 21 | // String captcha = (String) session.getAttribute("_captcha"); 22 | // if (!captcha.equalsIgnoreCase(code)) { 23 | // session.setAttribute("error", "验证码不正确"); 24 | // ((HttpServletResponse) response).sendRedirect("/adminlogin?error"); 25 | // return false; 26 | // } else { 27 | // return super.executeLogin(request, response); 28 | // } 29 | // } 30 | //} 31 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/service/BaseService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.config.service; 2 | 3 | /** 4 | * Created by tomoya. 5 | * Copyright (c) 2018, All Rights Reserved. 6 | * https://atjiu.github.io 7 | */ 8 | public interface BaseService { 9 | 10 | // 外接服务初始化实例方法 11 | T instance(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/service/SensitiveWordFilterService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.config.service; 2 | 3 | import co.yiiu.pybbs.model.SensitiveWord; 4 | import co.yiiu.pybbs.service.ISensitiveWordService; 5 | import co.yiiu.pybbs.util.SensitiveWordUtil; 6 | import org.springframework.context.annotation.DependsOn; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.annotation.PostConstruct; 10 | import javax.annotation.Resource; 11 | import java.util.HashSet; 12 | import java.util.List; 13 | import java.util.Set; 14 | 15 | /** 16 | * Created by tomoya. 17 | * Copyright (c) 2018, All Rights Reserved. 18 | * https://atjiu.github.io 19 | */ 20 | @Component 21 | @DependsOn("mybatisPlusConfig") 22 | public class SensitiveWordFilterService { 23 | 24 | @Resource 25 | private ISensitiveWordService sensitiveWordService; 26 | 27 | // 初始化过滤器 28 | @PostConstruct 29 | public void init() { 30 | List sensitiveWords = sensitiveWordService.selectAll(); 31 | Set sensitiveWordSet = new HashSet<>(); 32 | for (SensitiveWord sensitiveWord : sensitiveWords) { 33 | sensitiveWordSet.add(sensitiveWord.getWord()); 34 | } 35 | SensitiveWordUtil.init(sensitiveWordSet); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/websocket/MessageDecoder.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.config.websocket; 2 | 3 | import co.yiiu.pybbs.util.JsonUtil; 4 | import co.yiiu.pybbs.util.Message; 5 | 6 | import javax.websocket.Decoder; 7 | import javax.websocket.EndpointConfig; 8 | 9 | public class MessageDecoder implements Decoder.Text { 10 | @Override 11 | public Message decode(String s) { 12 | return JsonUtil.jsonToObject(s, Message.class); 13 | } 14 | 15 | @Override 16 | public boolean willDecode(String s) { 17 | // 验证json字符串是否合法,合法才会进入decode()方法进行转换,不合法直接抛异常 18 | return JsonUtil.isValid(s); 19 | } 20 | 21 | @Override 22 | public void init(EndpointConfig endpointConfig) { 23 | 24 | } 25 | 26 | @Override 27 | public void destroy() { 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/websocket/MessageEncoder.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.config.websocket; 2 | 3 | import co.yiiu.pybbs.util.JsonUtil; 4 | import co.yiiu.pybbs.util.Message; 5 | 6 | import javax.websocket.Encoder; 7 | import javax.websocket.EndpointConfig; 8 | 9 | public class MessageEncoder implements Encoder.Text { 10 | @Override 11 | public String encode(Message o) { 12 | return JsonUtil.objectToJson(o); 13 | } 14 | 15 | @Override 16 | public void init(EndpointConfig endpointConfig) { 17 | 18 | } 19 | 20 | @Override 21 | public void destroy() { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/config/websocket/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.config.websocket; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.socket.config.annotation.EnableWebSocket; 6 | import org.springframework.web.socket.server.standard.ServerEndpointExporter; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2018, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | @Configuration 14 | @EnableWebSocket 15 | public class WebSocketConfig { 16 | 17 | @Bean 18 | public ServerEndpointExporter serverEndpoint() { 19 | return new ServerEndpointExporter(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/controller/admin/BaseAdminController.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.controller.admin; 2 | 3 | import co.yiiu.pybbs.controller.api.BaseApiController; 4 | import co.yiiu.pybbs.model.AdminUser; 5 | import co.yiiu.pybbs.service.IAdminUserService; 6 | import org.apache.shiro.SecurityUtils; 7 | import org.apache.shiro.subject.Subject; 8 | 9 | import javax.annotation.Resource; 10 | 11 | /** 12 | * Created by tomoya. 13 | * Copyright (c) 2018, All Rights Reserved. 14 | * https://atjiu.github.io 15 | */ 16 | public class BaseAdminController extends BaseApiController { 17 | 18 | @Resource 19 | private IAdminUserService adminUserService; 20 | 21 | // 可以将传递到controller里的参数中Date类型的从String转成Date类型的对象 22 | // 这货没有想象中的好用,还不如我手动控制String转Date了 23 | // @InitBinder 24 | // public void initBinder(ServletRequestDataBinder binder) { 25 | // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 26 | // binder.registerCustomEditor(Date.class, new CustomDateEditor(sdf, true)); 27 | // } 28 | 29 | protected AdminUser getAdminUser() { 30 | Subject subject = SecurityUtils.getSubject(); 31 | String principal = (String) subject.getPrincipal(); 32 | return adminUserService.selectByUsername(principal); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/controller/admin/SystemConfigAdminController.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.controller.admin; 2 | 3 | import co.yiiu.pybbs.config.service.TelegramBotService; 4 | import co.yiiu.pybbs.service.ISystemConfigService; 5 | import co.yiiu.pybbs.util.Result; 6 | import org.apache.shiro.authz.annotation.RequiresPermissions; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.Model; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import javax.annotation.Resource; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | /** 19 | * Created by tomoya. 20 | * Copyright (c) 2018, All Rights Reserved. 21 | * https://atjiu.github.io 22 | */ 23 | @Controller 24 | @RequestMapping("/admin/system") 25 | public class SystemConfigAdminController extends BaseAdminController { 26 | 27 | private Logger log = LoggerFactory.getLogger(SystemConfigAdminController.class); 28 | 29 | @Resource 30 | private ISystemConfigService systemConfigService; 31 | @Resource 32 | private TelegramBotService telegramBotService; 33 | 34 | @RequiresPermissions("system:edit") 35 | @GetMapping("/edit") 36 | public String edit(Model model) { 37 | model.addAttribute("systems", systemConfigService.selectAll()); 38 | return "admin/system/edit"; 39 | } 40 | 41 | @RequiresPermissions("system:edit") 42 | @PostMapping("/edit") 43 | @ResponseBody 44 | public Result edit(@RequestBody List> list) { 45 | Map flattenedMap = new HashMap<>(); 46 | list.forEach(map -> flattenedMap.put(map.get("name"), map.get("value"))); 47 | new Thread(() -> telegramBotService.init().setWebHook(flattenedMap)).start(); 48 | 49 | systemConfigService.update(list); 50 | return success(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/controller/api/CollectApiController.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.controller.api; 2 | 3 | import co.yiiu.pybbs.exception.ApiAssert; 4 | import co.yiiu.pybbs.model.Collect; 5 | import co.yiiu.pybbs.model.User; 6 | import co.yiiu.pybbs.service.ICollectService; 7 | import co.yiiu.pybbs.util.Result; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * Created by tomoya. 14 | * Copyright (c) 2018, All Rights Reserved. 15 | * https://atjiu.github.io 16 | */ 17 | @RestController 18 | @RequestMapping("/api/collect") 19 | public class CollectApiController extends BaseApiController { 20 | 21 | @Resource 22 | private ICollectService collectService; 23 | 24 | // 收藏话题 25 | @PostMapping("/{topicId}") 26 | public Result get(@PathVariable Integer topicId) { 27 | User user = getApiUser(); 28 | Collect collect = collectService.selectByTopicIdAndUserId(topicId, user.getId()); 29 | ApiAssert.isNull(collect, "做人要知足,每人每个话题只能收藏一次哦!"); 30 | collectService.insert(topicId, user); 31 | return success(); 32 | } 33 | 34 | // 取消收藏 35 | @DeleteMapping("/{topicId}") 36 | public Result delete(@PathVariable Integer topicId) { 37 | User user = getApiUser(); 38 | Collect collect = collectService.selectByTopicIdAndUserId(topicId, user.getId()); 39 | ApiAssert.notNull(collect, "你都没有收藏这个话题,哪来的取消?"); 40 | collectService.delete(topicId, user.getId()); 41 | return success(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/controller/api/NotificationApiController.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.controller.api; 2 | 3 | import co.yiiu.pybbs.model.User; 4 | import co.yiiu.pybbs.service.INotificationService; 5 | import co.yiiu.pybbs.util.Result; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * Created by tomoya. 17 | * Copyright (c) 2018, All Rights Reserved. 18 | * https://atjiu.github.io 19 | */ 20 | @RestController 21 | @RequestMapping("/api/notification") 22 | public class NotificationApiController extends BaseApiController { 23 | 24 | @Resource 25 | private INotificationService notificationService; 26 | 27 | @GetMapping("/notRead") 28 | public Result notRead() { 29 | User user = getApiUser(); 30 | return success(notificationService.countNotRead(user.getId())); 31 | } 32 | 33 | @GetMapping("/markRead") 34 | public Result markRead() { 35 | User user = getApiUser(); 36 | notificationService.markRead(user.getId()); 37 | return success(); 38 | } 39 | 40 | // 通知列表 41 | @GetMapping("/list") 42 | public Result list() { 43 | User user = getApiUser(); 44 | // 未读消息列表 45 | List> notReadNotifications = notificationService.selectByUserId(user.getId(), false, 20); 46 | // 已读消息列表 47 | List> readNotifications = notificationService.selectByUserId(user.getId(), true, 20); 48 | Map map = new HashMap<>(); 49 | map.put("notRead", notReadNotifications); 50 | map.put("read", readNotifications); 51 | return success(map); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/controller/front/BaseController.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.controller.front; 2 | 3 | import co.yiiu.pybbs.model.User; 4 | import co.yiiu.pybbs.service.ISystemConfigService; 5 | import org.springframework.web.context.request.RequestContextHolder; 6 | import org.springframework.web.context.request.ServletRequestAttributes; 7 | 8 | import javax.annotation.Resource; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpSession; 11 | import java.util.Objects; 12 | 13 | /** 14 | * Created by tomoya. 15 | * Copyright (c) 2018, All Rights Reserved. 16 | * https://atjiu.github.io 17 | */ 18 | public class BaseController { 19 | 20 | @Resource 21 | private ISystemConfigService systemConfigService; 22 | 23 | protected String redirect(String path) { 24 | return "redirect:" + path; 25 | } 26 | 27 | protected User getUser() { 28 | HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder 29 | .getRequestAttributes())).getRequest(); 30 | HttpSession session = request.getSession(); 31 | return (User) session.getAttribute("_user"); 32 | } 33 | 34 | // 只针对前台页面的模板路径渲染,后台不变 35 | protected String render(String path) { 36 | return String.format("theme/%s/%s", systemConfigService.selectAllConfig().get("theme").toString(), path); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/controller/front/CommentController.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.controller.front; 2 | 3 | import co.yiiu.pybbs.model.Comment; 4 | import co.yiiu.pybbs.model.Topic; 5 | import co.yiiu.pybbs.service.ICommentService; 6 | import co.yiiu.pybbs.service.ITopicService; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.Model; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | 13 | import javax.annotation.Resource; 14 | 15 | /** 16 | * Created by tomoya. 17 | * Copyright (c) 2018, All Rights Reserved. 18 | * https://atjiu.github.io 19 | */ 20 | @Controller 21 | @RequestMapping("/comment") 22 | public class CommentController extends BaseController { 23 | 24 | @Resource 25 | private ICommentService commentService; 26 | @Resource 27 | private ITopicService topicService; 28 | 29 | // 编辑评论 30 | @GetMapping("/edit/{id}") 31 | public String edit(@PathVariable Integer id, Model model) { 32 | Comment comment = commentService.selectById(id); 33 | Topic topic = topicService.selectById(comment.getTopicId()); 34 | model.addAttribute("comment", comment); 35 | model.addAttribute("topic", topic); 36 | return render("comment/edit"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/controller/front/CommonController.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.controller.front; 2 | 3 | import co.yiiu.pybbs.controller.api.BaseApiController; 4 | import co.yiiu.pybbs.util.FileUtil; 5 | import co.yiiu.pybbs.util.captcha.Captcha; 6 | import co.yiiu.pybbs.util.captcha.GifCaptcha; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | 11 | import javax.annotation.Resource; 12 | import javax.servlet.http.HttpServletResponse; 13 | import javax.servlet.http.HttpSession; 14 | import java.io.IOException; 15 | 16 | /** 17 | * Created by tomoya. 18 | * Copyright (c) 2018, All Rights Reserved. 19 | * https://atjiu.github.io 20 | */ 21 | @Controller 22 | @RequestMapping("/common") 23 | public class CommonController extends BaseApiController { 24 | 25 | @Resource 26 | private FileUtil fileUtil; 27 | 28 | // gif 验证码 29 | @GetMapping("/captcha") 30 | public void captcha(HttpServletResponse response, HttpSession session) throws IOException { 31 | Captcha captcha = new GifCaptcha(); 32 | captcha.out(response.getOutputStream()); 33 | String text = captcha.text(); 34 | session.setAttribute("_captcha", text); 35 | } 36 | 37 | @GetMapping("/show_img") 38 | public String showOssImg(String name) { 39 | return "redirect:" + fileUtil.generatorOssUrl(name); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/directive/NotificationsDirective.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.directive; 2 | 3 | import co.yiiu.pybbs.service.INotificationService; 4 | import freemarker.core.Environment; 5 | import freemarker.template.*; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.annotation.Resource; 9 | import java.io.IOException; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * Created by tomoya. 15 | * Copyright (c) 2018, All Rights Reserved. 16 | * https://atjiu.github.io 17 | */ 18 | @Component 19 | public class NotificationsDirective implements TemplateDirectiveModel { 20 | 21 | @Resource 22 | private INotificationService notificationService; 23 | 24 | @Override 25 | public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody 26 | templateDirectiveBody) throws TemplateException, IOException { 27 | Integer userId = Integer.parseInt(map.get("userId").toString()); 28 | Boolean read = Integer.parseInt(map.get("read").toString()) == 1; 29 | // 如果想查询所有的消息,limit 传一个负数就可以了 比如 -1 30 | Integer limit = Integer.parseInt(map.get("limit").toString()); 31 | List> notifications = notificationService.selectByUserId(userId, read, limit); 32 | DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_28); 33 | environment.setVariable("notifications", builder.build().wrap(notifications)); 34 | templateDirectiveBody.render(environment.getOut()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/directive/OtherTopicDirective.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.directive; 2 | 3 | import co.yiiu.pybbs.model.Topic; 4 | import co.yiiu.pybbs.service.ITopicService; 5 | import freemarker.core.Environment; 6 | import freemarker.template.*; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.annotation.Resource; 10 | import java.io.IOException; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * Created by tomoya. 16 | * Copyright (c) 2018, All Rights Reserved. 17 | * https://atjiu.github.io 18 | */ 19 | @Component 20 | public class OtherTopicDirective implements TemplateDirectiveModel { 21 | 22 | @Resource 23 | private ITopicService topicService; 24 | 25 | @Override 26 | public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody 27 | templateDirectiveBody) throws TemplateException, IOException { 28 | Integer userId = Integer.parseInt(map.get("userId").toString()); 29 | Integer topicId = Integer.parseInt(map.get("topicId").toString()); 30 | Integer limit = Integer.parseInt(map.get("limit").toString()); 31 | List topics = topicService.selectAuthorOtherTopic(userId, topicId, limit); 32 | 33 | DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_28); 34 | environment.setVariable("topics", builder.build().wrap(topics)); 35 | templateDirectiveBody.render(environment.getOut()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/directive/ScoreDirective.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.directive; 2 | 3 | import co.yiiu.pybbs.model.User; 4 | import co.yiiu.pybbs.service.IUserService; 5 | import freemarker.core.Environment; 6 | import freemarker.template.*; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.annotation.Resource; 10 | import java.io.IOException; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * Created by tomoya. 16 | * Copyright (c) 2018, All Rights Reserved. 17 | * https://atjiu.github.io 18 | */ 19 | @Component 20 | public class ScoreDirective implements TemplateDirectiveModel { 21 | 22 | @Resource 23 | private IUserService userService; 24 | 25 | @Override 26 | public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody 27 | templateDirectiveBody) throws TemplateException, IOException { 28 | Integer limit = Integer.parseInt(map.get("limit").toString()); 29 | if (limit > 100) limit = 100; 30 | List users = userService.selectTop(limit); 31 | 32 | DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_28); 33 | environment.setVariable("users", builder.build().wrap(users)); 34 | templateDirectiveBody.render(environment.getOut()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/directive/SearchDirective.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.directive; 2 | 3 | import co.yiiu.pybbs.service.ISystemConfigService; 4 | import co.yiiu.pybbs.service.ITopicService; 5 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 6 | import freemarker.core.Environment; 7 | import freemarker.template.*; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.util.StringUtils; 10 | 11 | import javax.annotation.Resource; 12 | import java.io.IOException; 13 | import java.util.Map; 14 | 15 | /** 16 | * Created by tomoya. 17 | * Copyright (c) 2018, All Rights Reserved. 18 | * https://atjiu.github.io 19 | */ 20 | @Component 21 | public class SearchDirective implements TemplateDirectiveModel { 22 | 23 | @Resource 24 | private ISystemConfigService systemConfigService; 25 | @Resource 26 | private ITopicService topicService; 27 | 28 | @Override 29 | public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody 30 | templateDirectiveBody) throws TemplateException, IOException { 31 | Page> page = new Page<>(); 32 | String keyword = String.valueOf(map.get("keyword")); 33 | Integer pageNo = Integer.parseInt(map.get("pageNo").toString()); 34 | if (!StringUtils.isEmpty(keyword)) { 35 | Integer pageSize = Integer.parseInt(systemConfigService.selectAllConfig().get("page_size").toString()); 36 | // page = elasticSearchService.searchDocument(pageNo, pageSize, keyword, "title", "content"); 37 | page = topicService.search(pageNo, pageSize, keyword); 38 | } 39 | 40 | DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_28); 41 | environment.setVariable("page", builder.build().wrap(page)); 42 | templateDirectiveBody.render(environment.getOut()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/directive/SocialDirective.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.directive; 2 | 3 | import co.yiiu.pybbs.plugin.SocialPlugin; 4 | import freemarker.core.Environment; 5 | import freemarker.template.*; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.annotation.Resource; 9 | import java.io.IOException; 10 | import java.util.Map; 11 | 12 | /** 13 | * 社交化登录相关的自定义标签 14 | * 15 | * @author yadong.zhang (yadong.zhang0415(a)gmail.com) 16 | * @version 1.0.0 17 | * @date 2020/6/25 0:07 18 | * @since 1.0.0 19 | */ 20 | @Component 21 | public class SocialDirective implements TemplateDirectiveModel { 22 | 23 | @Resource 24 | private SocialPlugin socialPlugin; 25 | 26 | @Override 27 | public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody 28 | templateDirectiveBody) throws TemplateException, IOException { 29 | DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_28); 30 | environment.setVariable("socialList", builder.build().wrap(socialPlugin.getAllAvailableSocialPlatform())); 31 | templateDirectiveBody.render(environment.getOut()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/directive/TagsDirective.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.directive; 2 | 3 | import co.yiiu.pybbs.model.Tag; 4 | import co.yiiu.pybbs.service.ITagService; 5 | import com.baomidou.mybatisplus.core.metadata.IPage; 6 | import freemarker.core.Environment; 7 | import freemarker.template.*; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.annotation.Resource; 11 | import java.io.IOException; 12 | import java.util.Map; 13 | 14 | /** 15 | * Created by tomoya. 16 | * Copyright (c) 2018, All Rights Reserved. 17 | * https://atjiu.github.io 18 | */ 19 | @Component 20 | public class TagsDirective implements TemplateDirectiveModel { 21 | 22 | @Resource 23 | private ITagService tagService; 24 | 25 | @Override 26 | public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody 27 | templateDirectiveBody) throws TemplateException, IOException { 28 | Integer pageNo = Integer.parseInt(map.get("pageNo").toString()); 29 | Integer pageSize = Integer.parseInt(map.get("pageSize").toString()); 30 | IPage page = tagService.selectAll(pageNo, pageSize, null); 31 | 32 | DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_28); 33 | environment.setVariable("page", builder.build().wrap(page)); 34 | templateDirectiveBody.render(environment.getOut()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/directive/TopicCommentsDirective.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.directive; 2 | 3 | import co.yiiu.pybbs.service.ICommentService; 4 | import freemarker.core.Environment; 5 | import freemarker.template.*; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.annotation.Resource; 9 | import java.io.IOException; 10 | import java.util.Map; 11 | 12 | /** 13 | * Created by tomoya. 14 | * Copyright (c) 2018, All Rights Reserved. 15 | * https://atjiu.github.io 16 | */ 17 | @Component 18 | public class TopicCommentsDirective implements TemplateDirectiveModel { 19 | 20 | @Resource 21 | private ICommentService commentService; 22 | 23 | @Override 24 | public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody 25 | templateDirectiveBody) throws TemplateException, IOException { 26 | Integer topicId = Integer.parseInt(map.get("topicId").toString()); 27 | DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_28); 28 | environment.setVariable("comments", builder.build().wrap(commentService.selectByTopicId(topicId))); 29 | templateDirectiveBody.render(environment.getOut()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/directive/TopicListDirective.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.directive; 2 | 3 | import co.yiiu.pybbs.service.ITopicService; 4 | import co.yiiu.pybbs.util.MyPage; 5 | import freemarker.core.Environment; 6 | import freemarker.template.*; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.annotation.Resource; 10 | import java.io.IOException; 11 | import java.util.Map; 12 | 13 | /** 14 | * Created by tomoya. 15 | * Copyright (c) 2018, All Rights Reserved. 16 | * https://atjiu.github.io 17 | */ 18 | @Component 19 | public class TopicListDirective implements TemplateDirectiveModel { 20 | 21 | @Resource 22 | private ITopicService topicService; 23 | 24 | @Override 25 | public void execute(Environment environment, Map map, TemplateModel[] templateModels, 26 | TemplateDirectiveBody templateDirectiveBody) throws TemplateException, IOException { 27 | Integer pageNo = Integer.parseInt(map.get("pageNo").toString()); 28 | String tab = map.get("tab").toString(); 29 | MyPage> page = topicService.selectAll(pageNo, tab); 30 | 31 | DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_28); 32 | environment.setVariable("page", builder.build().wrap(page)); 33 | templateDirectiveBody.render(environment.getOut()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/directive/UserCollectsDirective.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.directive; 2 | 3 | import co.yiiu.pybbs.model.User; 4 | import co.yiiu.pybbs.service.ICollectService; 5 | import co.yiiu.pybbs.service.IUserService; 6 | import freemarker.core.Environment; 7 | import freemarker.template.*; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.annotation.Resource; 11 | import java.io.IOException; 12 | import java.util.Map; 13 | 14 | /** 15 | * Created by tomoya. 16 | * Copyright (c) 2018, All Rights Reserved. 17 | * https://atjiu.github.io 18 | */ 19 | @Component 20 | public class UserCollectsDirective implements TemplateDirectiveModel { 21 | 22 | @Resource 23 | private ICollectService collectService; 24 | @Resource 25 | private IUserService userService; 26 | 27 | @Override 28 | public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody 29 | templateDirectiveBody) throws TemplateException, IOException { 30 | String username = String.valueOf(map.get("username")); 31 | Integer pageNo = Integer.parseInt(map.get("pageNo").toString()); 32 | User user = userService.selectByUsername(username); 33 | DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_28); 34 | environment.setVariable("collects", builder.build().wrap(collectService.selectByUserId(user.getId(), pageNo, 35 | null))); 36 | templateDirectiveBody.render(environment.getOut()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/directive/UserCommentsDirective.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.directive; 2 | 3 | import co.yiiu.pybbs.model.User; 4 | import co.yiiu.pybbs.service.ICommentService; 5 | import co.yiiu.pybbs.service.IUserService; 6 | import freemarker.core.Environment; 7 | import freemarker.template.*; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.annotation.Resource; 11 | import java.io.IOException; 12 | import java.util.Map; 13 | 14 | /** 15 | * Created by tomoya. 16 | * Copyright (c) 2018, All Rights Reserved. 17 | * https://atjiu.github.io 18 | */ 19 | @Component 20 | public class UserCommentsDirective implements TemplateDirectiveModel { 21 | 22 | @Resource 23 | private ICommentService commentService; 24 | @Resource 25 | private IUserService userService; 26 | 27 | @Override 28 | public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody 29 | templateDirectiveBody) throws TemplateException, IOException { 30 | String username = String.valueOf(map.get("username")); 31 | Integer pageNo = Integer.parseInt(map.get("pageNo").toString()); 32 | Integer pageSize = map.get("pageSize") == null ? null : Integer.parseInt(map.get("pageSize").toString()); 33 | User user = userService.selectByUsername(username); 34 | DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_28); 35 | environment.setVariable("comments", builder.build().wrap(commentService.selectByUserId(user.getId(), pageNo, 36 | pageSize))); 37 | templateDirectiveBody.render(environment.getOut()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/directive/UserTopicsDirective.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.directive; 2 | 3 | import co.yiiu.pybbs.model.User; 4 | import co.yiiu.pybbs.service.ITopicService; 5 | import co.yiiu.pybbs.service.IUserService; 6 | import freemarker.core.Environment; 7 | import freemarker.template.*; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.annotation.Resource; 11 | import java.io.IOException; 12 | import java.util.Map; 13 | 14 | /** 15 | * Created by tomoya. 16 | * Copyright (c) 2018, All Rights Reserved. 17 | * https://atjiu.github.io 18 | */ 19 | @Component 20 | public class UserTopicsDirective implements TemplateDirectiveModel { 21 | 22 | @Resource 23 | private ITopicService topicService; 24 | @Resource 25 | private IUserService userService; 26 | 27 | @Override 28 | public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody 29 | templateDirectiveBody) throws TemplateException, IOException { 30 | String username = String.valueOf(map.get("username")); 31 | Integer pageNo = Integer.parseInt(map.get("pageNo").toString()); 32 | Integer pageSize = map.get("pageSize") == null ? null : Integer.parseInt(map.get("pageSize").toString()); 33 | User user = userService.selectByUsername(username); 34 | DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_28); 35 | environment.setVariable("topics", builder.build().wrap(topicService.selectByUserId(user.getId(), pageNo, 36 | pageSize))); 37 | templateDirectiveBody.render(environment.getOut()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/exception/ApiException.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.exception; 2 | 3 | /** 4 | * Created by tomoya. 5 | * Copyright (c) 2018, All Rights Reserved. 6 | * https://atjiu.github.io 7 | */ 8 | public class ApiException extends RuntimeException { 9 | 10 | private static final long serialVersionUID = 1L; 11 | private int code; 12 | private String message; 13 | 14 | public ApiException(String message) { 15 | this.code = 201; 16 | this.message = message; 17 | } 18 | 19 | public ApiException(int code, String message) { 20 | this.code = code; 21 | this.message = message; 22 | } 23 | 24 | public int getCode() { 25 | return code; 26 | } 27 | 28 | public void setCode(int code) { 29 | this.code = code; 30 | } 31 | 32 | @Override 33 | public String getMessage() { 34 | return message; 35 | } 36 | 37 | public void setMessage(String message) { 38 | this.message = message; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/hook/CommentServiceHook.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.hook; 2 | 3 | import org.aspectj.lang.annotation.Pointcut; 4 | 5 | /** 6 | * Created by tomoya. 7 | * Copyright (c) 2018, All Rights Reserved. 8 | * https://atjiu.github.io 9 | */ 10 | public class CommentServiceHook { 11 | 12 | @Pointcut("execution(public * co.yiiu.pybbs.service.ICommentService.selectByTopicId(..))") 13 | public void selectByTopicId() { 14 | } 15 | 16 | @Pointcut("execution(public * co.yiiu.pybbs.service.ICommentService.insert(..))") 17 | public void insert() { 18 | } 19 | 20 | @Pointcut("execution(public * co.yiiu.pybbs.service.ICommentService.update(..))") 21 | public void update() { 22 | } 23 | 24 | @Pointcut("execution(public * co.yiiu.pybbs.service.ICommentService.vote(..))") 25 | public void vote() { 26 | } 27 | 28 | @Pointcut("execution(public * co.yiiu.pybbs.service.ICommentService.delete(..))") 29 | public void delete() { 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/hook/FileUtilHook.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.hook; 2 | 3 | import org.aspectj.lang.annotation.Pointcut; 4 | 5 | public class FileUtilHook { 6 | 7 | @Pointcut("execution(public * co.yiiu.pybbs.util.FileUtil.upload(..))") 8 | public void upload() { 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/hook/IndexedServiceHook.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.hook; 2 | 3 | import org.aspectj.lang.annotation.Pointcut; 4 | 5 | /** 6 | * Created by tomoya. 7 | * Copyright (c) 2018, All Rights Reserved. 8 | * https://atjiu.github.io 9 | */ 10 | public class IndexedServiceHook { 11 | 12 | @Pointcut("execution(public * co.yiiu.pybbs.service.IIndexedService.indexAllTopic(..))") 13 | public void indexAllTopic() { 14 | } 15 | 16 | @Pointcut("execution(public * co.yiiu.pybbs.service.IIndexedService.indexTopic(..))") 17 | public void indexTopic() { 18 | } 19 | 20 | @Pointcut("execution(public * co.yiiu.pybbs.service.IIndexedService.deleteTopicIndex(..))") 21 | public void deleteTopicIndex() { 22 | } 23 | 24 | @Pointcut("execution(public * co.yiiu.pybbs.service.IIndexedService.batchDeleteIndex(..))") 25 | public void batchDeleteIndex() { 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/hook/TopicServiceHook.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.hook; 2 | 3 | import org.aspectj.lang.annotation.Pointcut; 4 | 5 | /** 6 | * Created by tomoya. 7 | * Copyright (c) 2018, All Rights Reserved. 8 | * https://atjiu.github.io 9 | */ 10 | public class TopicServiceHook { 11 | 12 | @Pointcut("execution(public * co.yiiu.pybbs.service.ITopicService.search(..))") 13 | public void search() { 14 | } 15 | 16 | @Pointcut("execution(public * co.yiiu.pybbs.service.ITopicService.selectById(..))") 17 | public void selectById() { 18 | } 19 | 20 | @Pointcut("execution(public * co.yiiu.pybbs.service.ITopicService.update(..))") 21 | public void update() { 22 | } 23 | 24 | @Pointcut("execution(public * co.yiiu.pybbs.service.ITopicService.vote(..))") 25 | public void vote() { 26 | } 27 | 28 | @Pointcut("execution(public * co.yiiu.pybbs.service.ITopicService.updateViewCount(..))") 29 | public void updateViewCount() { 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/hook/UserServiceHook.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.hook; 2 | 3 | import org.aspectj.lang.annotation.Pointcut; 4 | 5 | /** 6 | * Created by tomoya. 7 | * Copyright (c) 2018, All Rights Reserved. 8 | * https://atjiu.github.io 9 | */ 10 | public class UserServiceHook { 11 | 12 | @Pointcut("execution(public * co.yiiu.pybbs.service.IUserService.selectByUsername(..))") 13 | public void selectByUsername() { 14 | } 15 | 16 | @Pointcut("execution(public * co.yiiu.pybbs.service.IUserService.selectByToken(..))") 17 | public void selectByToken() { 18 | } 19 | 20 | @Pointcut("execution(public * co.yiiu.pybbs.service.IUserService.selectById(..))") 21 | public void selectById() { 22 | } 23 | 24 | @Pointcut("execution(public * co.yiiu.pybbs.service.IUserService.delRedisUser(..))") 25 | public void delRedisUser() { 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/interceptor/UserInterceptor.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.interceptor; 2 | 3 | import co.yiiu.pybbs.model.User; 4 | import co.yiiu.pybbs.service.ISystemConfigService; 5 | import co.yiiu.pybbs.service.impl.UserService; 6 | import co.yiiu.pybbs.util.CookieUtil; 7 | import co.yiiu.pybbs.util.HttpUtil; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.util.StringUtils; 10 | import org.springframework.web.servlet.HandlerInterceptor; 11 | 12 | import javax.annotation.Resource; 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import javax.servlet.http.HttpSession; 16 | 17 | /** 18 | * Created by tomoya. 19 | * Copyright (c) 2018, All Rights Reserved. 20 | * https://atjiu.github.io 21 | */ 22 | @Component 23 | public class UserInterceptor implements HandlerInterceptor { 24 | 25 | @Resource 26 | private CookieUtil cookieUtil; 27 | @Resource 28 | private ISystemConfigService systemConfigService; 29 | @Resource 30 | private UserService userService; 31 | 32 | @Override 33 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 34 | HttpSession session = request.getSession(); 35 | User user = (User) session.getAttribute("_user"); 36 | if (user == null) { 37 | String token = cookieUtil.getCookie(systemConfigService.selectAllConfig().get("cookie_name").toString()); 38 | if (!StringUtils.isEmpty(token)) { 39 | user = userService.selectByToken(token); 40 | session.setAttribute("_user", user); 41 | } 42 | } 43 | if (user == null) { 44 | HttpUtil.responseWrite(request, response); 45 | return false; 46 | } else { 47 | return true; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/AdminUserMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.AdminUser; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * Created by tomoya. 11 | * Copyright (c) 2018, All Rights Reserved. 12 | * https://atjiu.github.io 13 | */ 14 | public interface AdminUserMapper extends BaseMapper { 15 | 16 | List> selectAll(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/AdminUserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/CodeMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.Code; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | public interface CodeMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/CollectMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.Collect; 4 | import co.yiiu.pybbs.util.MyPage; 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | * Created by tomoya. 12 | * Copyright (c) 2018, All Rights Reserved. 13 | * https://atjiu.github.io 14 | */ 15 | public interface CollectMapper extends BaseMapper { 16 | 17 | MyPage> selectByUserId(MyPage> iPage, @Param("userId") Integer userId); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/CollectMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 13 | 14 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/CommentMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.Comment; 4 | import co.yiiu.pybbs.model.vo.CommentsByTopic; 5 | import co.yiiu.pybbs.util.MyPage; 6 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 7 | import org.apache.ibatis.annotations.Param; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * Created by tomoya. 14 | * Copyright (c) 2018, All Rights Reserved. 15 | * https://atjiu.github.io 16 | */ 17 | public interface CommentMapper extends BaseMapper { 18 | 19 | List selectByTopicId(@Param("topicId") Integer topicId); 20 | 21 | MyPage> selectByUserId(MyPage> iPage, @Param("userId") Integer userId); 22 | 23 | MyPage> selectAllForAdmin(MyPage> iPage, @Param("startDate") String 24 | startDate, @Param("endDate") String endDate, @Param("username") String username); 25 | 26 | int countToday(); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/NotificationMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.Notification; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.apache.ibatis.annotations.Select; 7 | import org.apache.ibatis.annotations.Update; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * Created by tomoya. 14 | * Copyright (c) 2018, All Rights Reserved. 15 | * https://atjiu.github.io 16 | */ 17 | public interface NotificationMapper extends BaseMapper { 18 | 19 | List> selectByUserId(@Param("userId") Integer userId, @Param("read") Boolean read, @Param 20 | ("limit") Integer limit); 21 | 22 | // 查询未读消息数量 23 | @Select("select count(1) from notification where target_user_id = #{userId} and `read` = false") 24 | long countNotRead(@Param("userId") Integer userId); 25 | 26 | // 将未读消息置为已读 27 | @Update("update notification set `read` = true where target_user_id = #{targetUserId}") 28 | void updateNotificationStatus(@Param("targetUserId") Integer targetUserId); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/NotificationMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 13 | 14 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/OAuthUserMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.OAuthUser; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | public interface OAuthUserMapper extends BaseMapper { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/PermissionMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.Permission; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | public interface PermissionMapper extends BaseMapper { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/RoleMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.Role; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | public interface RoleMapper extends BaseMapper { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/RolePermissionMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.RolePermission; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | public interface RolePermissionMapper extends BaseMapper { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/SensitiveWordMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.SensitiveWord; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | public interface SensitiveWordMapper extends BaseMapper { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/SystemConfigMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.SystemConfig; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | public interface SystemConfigMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/SystemConfigMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | `key`, `value`, `description` 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/TagMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.Tag; 4 | import co.yiiu.pybbs.util.MyPage; 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | * Created by tomoya. 12 | * Copyright (c) 2018, All Rights Reserved. 13 | * https://atjiu.github.io 14 | */ 15 | public interface TagMapper extends BaseMapper { 16 | 17 | MyPage> selectTopicByTagId(MyPage> iPage, @Param("tagId") Integer tagId); 18 | 19 | int countToday(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/TagMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 13 | 14 | 23 | 24 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/TopicMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.Topic; 4 | import co.yiiu.pybbs.util.MyPage; 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | * Created by tomoya. 12 | * Copyright (c) 2018, All Rights Reserved. 13 | * https://atjiu.github.io 14 | */ 15 | public interface TopicMapper extends BaseMapper { 16 | 17 | MyPage> selectAll(MyPage> iPage, @Param("tab") String tab); 18 | 19 | MyPage> selectByTag(MyPage> iPage, @Param("tag") String tag); 20 | 21 | MyPage> selectByUserId(MyPage> iPage, @Param("userId") Integer userId); 22 | 23 | MyPage> selectAllForAdmin(MyPage> iPage, @Param("startDate") String 24 | startDate, @Param("endDate") String endDate, @Param("username") String username); 25 | 26 | int countToday(); 27 | 28 | MyPage> search(MyPage> iPage, @Param("keyword") String keyword); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/TopicTagMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.TopicTag; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | public interface TopicTagMapper extends BaseMapper { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.mapper; 2 | 3 | import co.yiiu.pybbs.model.User; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * Created by tomoya. 9 | * Copyright (c) 2018, All Rights Reserved. 10 | * https://atjiu.github.io 11 | */ 12 | @Mapper 13 | public interface UserMapper extends BaseMapper { 14 | int countToday(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/model/AdminUser.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | 6 | import java.io.Serializable; 7 | import java.util.Date; 8 | 9 | /** 10 | * Created by tomoya. 11 | * Copyright (c) 2018, All Rights Reserved. 12 | * https://atjiu.github.io 13 | */ 14 | public class AdminUser implements Serializable { 15 | 16 | private static final long serialVersionUID = 8264158018518861440L; 17 | @TableId(type = IdType.AUTO) 18 | private Integer id; 19 | private String username; 20 | private String password; 21 | private Date inTime; 22 | private Integer roleId; 23 | 24 | public Integer getId() { 25 | return id; 26 | } 27 | 28 | public void setId(Integer id) { 29 | this.id = id; 30 | } 31 | 32 | public String getUsername() { 33 | return username; 34 | } 35 | 36 | public void setUsername(String username) { 37 | this.username = username; 38 | } 39 | 40 | public String getPassword() { 41 | return password; 42 | } 43 | 44 | public void setPassword(String password) { 45 | this.password = password; 46 | } 47 | 48 | public Date getInTime() { 49 | return inTime; 50 | } 51 | 52 | public void setInTime(Date inTime) { 53 | this.inTime = inTime; 54 | } 55 | 56 | public Integer getRoleId() { 57 | return roleId; 58 | } 59 | 60 | public void setRoleId(Integer roleId) { 61 | this.roleId = roleId; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/model/Collect.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.model; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | public class Collect implements Serializable { 12 | 13 | private static final long serialVersionUID = 7610730966340643542L; 14 | private Integer topicId; 15 | private Integer userId; 16 | private Date inTime; 17 | 18 | public Integer getTopicId() { 19 | return topicId; 20 | } 21 | 22 | public void setTopicId(Integer topicId) { 23 | this.topicId = topicId; 24 | } 25 | 26 | public Integer getUserId() { 27 | return userId; 28 | } 29 | 30 | public void setUserId(Integer userId) { 31 | this.userId = userId; 32 | } 33 | 34 | public Date getInTime() { 35 | return inTime; 36 | } 37 | 38 | public void setInTime(Date inTime) { 39 | this.inTime = inTime; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/model/Permission.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2018, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | public class Permission implements Serializable { 14 | private static final long serialVersionUID = -2694960432845360318L; 15 | @TableId(type = IdType.AUTO) 16 | private Integer id; 17 | private String name; 18 | private String value; 19 | // 权限的父节点的id 20 | private Integer pid; 21 | 22 | public Integer getId() { 23 | return id; 24 | } 25 | 26 | public void setId(Integer id) { 27 | this.id = id; 28 | } 29 | 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | public void setName(String name) { 35 | this.name = name; 36 | } 37 | 38 | public String getValue() { 39 | return value; 40 | } 41 | 42 | public void setValue(String value) { 43 | this.value = value; 44 | } 45 | 46 | public Integer getPid() { 47 | return pid; 48 | } 49 | 50 | public void setPid(Integer pid) { 51 | this.pid = pid; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/model/Role.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2018, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | public class Role implements Serializable { 14 | private static final long serialVersionUID = 7824693669858106664L; 15 | @TableId(type = IdType.AUTO) 16 | private Integer id; 17 | private String name; 18 | 19 | public Integer getId() { 20 | return id; 21 | } 22 | 23 | public void setId(Integer id) { 24 | this.id = id; 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/model/RolePermission.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.model; 2 | 3 | /** 4 | * Created by tomoya. 5 | * Copyright (c) 2018, All Rights Reserved. 6 | * https://atjiu.github.io 7 | */ 8 | public class RolePermission { 9 | 10 | private Integer roleId; 11 | private Integer permissionId; 12 | 13 | public Integer getRoleId() { 14 | return roleId; 15 | } 16 | 17 | public void setRoleId(Integer roleId) { 18 | this.roleId = roleId; 19 | } 20 | 21 | public Integer getPermissionId() { 22 | return permissionId; 23 | } 24 | 25 | public void setPermissionId(Integer permissionId) { 26 | this.permissionId = permissionId; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/model/SensitiveWord.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | public class SensitiveWord { 12 | 13 | @TableId(type = IdType.AUTO) 14 | private Integer id; 15 | private String word; 16 | 17 | public Integer getId() { 18 | return id; 19 | } 20 | 21 | public void setId(Integer id) { 22 | this.id = id; 23 | } 24 | 25 | public String getWord() { 26 | return word; 27 | } 28 | 29 | public void setWord(String word) { 30 | this.word = word; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/model/Tag.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | 6 | import java.io.Serializable; 7 | import java.util.Date; 8 | 9 | /** 10 | * Created by tomoya. 11 | * Copyright (c) 2018, All Rights Reserved. 12 | * https://atjiu.github.io 13 | */ 14 | public class Tag implements Serializable { 15 | private static final long serialVersionUID = 3257790983905872243L; 16 | @TableId(type = IdType.AUTO) 17 | private Integer id; 18 | private String name; 19 | private String description; 20 | private String icon; 21 | // 当前标签下的话题个数 22 | private Integer topicCount; 23 | private Date inTime; 24 | 25 | public Date getInTime() { 26 | return inTime; 27 | } 28 | 29 | public void setInTime(Date inTime) { 30 | this.inTime = inTime; 31 | } 32 | 33 | public Integer getId() { 34 | return id; 35 | } 36 | 37 | public void setId(Integer id) { 38 | this.id = id; 39 | } 40 | 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | public void setName(String name) { 46 | this.name = name; 47 | } 48 | 49 | public String getDescription() { 50 | return description; 51 | } 52 | 53 | public void setDescription(String description) { 54 | this.description = description; 55 | } 56 | 57 | public String getIcon() { 58 | return icon; 59 | } 60 | 61 | public void setIcon(String icon) { 62 | this.icon = icon; 63 | } 64 | 65 | public Integer getTopicCount() { 66 | return topicCount; 67 | } 68 | 69 | public void setTopicCount(Integer topicCount) { 70 | this.topicCount = topicCount; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/model/TopicTag.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.model; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by tomoya. 7 | * Copyright (c) 2018, All Rights Reserved. 8 | * https://atjiu.github.io 9 | */ 10 | public class TopicTag implements Serializable { 11 | private static final long serialVersionUID = -5028599844989220715L; 12 | private Integer tagId; 13 | private Integer topicId; 14 | 15 | public static long getSerialVersionUID() { 16 | return serialVersionUID; 17 | } 18 | 19 | public Integer getTagId() { 20 | return tagId; 21 | } 22 | 23 | public void setTagId(Integer tagId) { 24 | this.tagId = tagId; 25 | } 26 | 27 | public Integer getTopicId() { 28 | return topicId; 29 | } 30 | 31 | public void setTopicId(Integer topicId) { 32 | this.topicId = topicId; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/model/vo/CommentsByTopic.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.model.vo; 2 | 3 | import co.yiiu.pybbs.model.Comment; 4 | 5 | import java.io.Serializable; 6 | import java.util.LinkedHashMap; 7 | import java.util.List; 8 | 9 | /** 10 | * Created by tomoya. 11 | * Copyright (c) 2018, All Rights Reserved. 12 | * https://atjiu.github.io 13 | */ 14 | public class CommentsByTopic extends Comment implements Serializable { 15 | private static final long serialVersionUID = 8082073760910701836L; 16 | // 话题下面的评论列表单个对象的数据结构 17 | 18 | private String username; 19 | private String avatar; 20 | // 评论的层级,直接评论话题的,layer即为0,如果回复了评论的,则当前回复的layer为评论对象的layer+1 21 | private Integer layer; 22 | 23 | private LinkedHashMap> children; 24 | 25 | public String getUsername() { 26 | return username; 27 | } 28 | 29 | public void setUsername(String username) { 30 | this.username = username; 31 | } 32 | 33 | public String getAvatar() { 34 | return avatar; 35 | } 36 | 37 | public void setAvatar(String avatar) { 38 | this.avatar = avatar; 39 | } 40 | 41 | public Integer getLayer() { 42 | return layer; 43 | } 44 | 45 | public void setLayer(Integer layer) { 46 | this.layer = layer; 47 | } 48 | 49 | public LinkedHashMap> getChildren() { 50 | return children; 51 | } 52 | 53 | public void setChildren(LinkedHashMap> children) { 54 | this.children = children; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/model/vo/UserWithWebSocketVO.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.model.vo; 2 | 3 | 4 | import java.io.Serializable; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | public class UserWithWebSocketVO implements Serializable { 12 | 13 | private static final long serialVersionUID = -8327007303087296114L; 14 | private String username; 15 | private Integer userId; 16 | 17 | public UserWithWebSocketVO() { 18 | } 19 | 20 | public UserWithWebSocketVO(String username, Integer userId) { 21 | this.username = username; 22 | this.userId = userId; 23 | } 24 | 25 | public String getUsername() { 26 | return username; 27 | } 28 | 29 | public void setUsername(String username) { 30 | this.username = username; 31 | } 32 | 33 | public Integer getUserId() { 34 | return userId; 35 | } 36 | 37 | public void setUserId(Integer userId) { 38 | this.userId = userId; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/model/vo/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tomoya. 3 | * Copyright (c) 2018, All Rights Reserved. 4 | * https://atjiu.github.io 5 | */ 6 | package co.yiiu.pybbs.model.vo; 7 | 8 | /** 9 | * 这里放的是一些Map转json后,再转回Map出现问题的类,用作确定数据类型的,没有其它实际意义 10 | */ 11 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/plugin/FileUploadPlugin.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.plugin; 2 | 3 | import co.yiiu.pybbs.service.ISystemConfigService; 4 | import co.yiiu.pybbs.util.FileUtil; 5 | import org.aspectj.lang.ProceedingJoinPoint; 6 | import org.aspectj.lang.annotation.Around; 7 | import org.aspectj.lang.annotation.Aspect; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.util.StringUtils; 12 | import org.springframework.web.multipart.MultipartFile; 13 | 14 | import javax.annotation.Resource; 15 | 16 | @Component 17 | @Aspect 18 | public class FileUploadPlugin { 19 | 20 | private final Logger log = LoggerFactory.getLogger(FileUploadPlugin.class); 21 | @Resource 22 | private ISystemConfigService systemConfigService; 23 | @Resource 24 | private FileUtil fileUtil; 25 | 26 | @Around("co.yiiu.pybbs.hook.FileUtilHook.upload()") 27 | public Object upload(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 28 | String cloudStoragePlatform = systemConfigService.selectAllConfig().get("cloud_storage_platform"); 29 | log.info("fileUtilHook.upload: {}", cloudStoragePlatform); 30 | Object[] args = proceedingJoinPoint.getArgs(); 31 | if (StringUtils.isEmpty(cloudStoragePlatform) || UploadPlatForm.LOCAL.name().equals(cloudStoragePlatform)) { 32 | return proceedingJoinPoint.proceed(args); 33 | } else if (UploadPlatForm.QINIU.name().equals(cloudStoragePlatform)) { 34 | return fileUtil.qiniuUpload((MultipartFile) args[0], (String) args[1], (String) args[2]); 35 | } else if (UploadPlatForm.OSS.name().equals(cloudStoragePlatform)) { 36 | return fileUtil.ossUpload((MultipartFile) args[0], (String) args[1], (String) args[2]); 37 | } 38 | return null; 39 | } 40 | 41 | enum UploadPlatForm { 42 | LOCAL, QINIU, OSS 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/IAdminUserService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.AdminUser; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2018, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | public interface IAdminUserService { 14 | // 根据用户名查询用户 15 | AdminUser selectByUsername(String username); 16 | 17 | // 查询所有的后台用户 18 | List> selectAll(); 19 | 20 | void update(AdminUser adminUser); 21 | 22 | void insert(AdminUser adminUser); 23 | 24 | void delete(Integer id); 25 | 26 | AdminUser selectById(Integer id); 27 | 28 | // 根据角色id查询后台关联的用户 29 | List selectByRoleId(Integer roleId); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/ICodeService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.Code; 4 | 5 | /** 6 | * Created by tomoya. 7 | * Copyright (c) 2018, All Rights Reserved. 8 | * https://atjiu.github.io 9 | */ 10 | public interface ICodeService { 11 | Code selectByCode(String _code); 12 | 13 | // 查询没有用过的code 14 | Code selectNotUsedCode(Integer userId, String email, String mobile); 15 | 16 | // 创建一条验证码记录 17 | Code createCode(Integer userId, String email, String mobile); 18 | 19 | // 验证邮箱验证码 20 | Code validateCode(Integer userId, String email, String mobile, String _code); 21 | 22 | // 发送邮件 23 | boolean sendEmail(Integer userId, String email, String title, String content); 24 | 25 | // 发送短信 26 | boolean sendSms(String mobile); 27 | 28 | void update(Code code); 29 | 30 | // 根据用户id删除评论记录 31 | void deleteByUserId(Integer userId); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/ICollectService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.Collect; 4 | import co.yiiu.pybbs.model.User; 5 | import co.yiiu.pybbs.util.MyPage; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * Created by tomoya. 12 | * Copyright (c) 2018, All Rights Reserved. 13 | * https://atjiu.github.io 14 | */ 15 | public interface ICollectService { 16 | // 查询话题被多少人收藏过 17 | List selectByTopicId(Integer topicId); 18 | 19 | // 查询用户是否收藏过某个话题 20 | Collect selectByTopicIdAndUserId(Integer topicId, Integer userId); 21 | 22 | // 收藏话题 23 | Collect insert(Integer topicId, User user); 24 | 25 | // 删除(取消)收藏 26 | void delete(Integer topicId, Integer userId); 27 | 28 | // 根据话题id删除收藏记录 29 | void deleteByTopicId(Integer topicId); 30 | 31 | // 根据用户id删除收藏记录 32 | void deleteByUserId(Integer userId); 33 | 34 | // 查询用户收藏的话题数 35 | int countByUserId(Integer userId); 36 | 37 | // 查询用户收藏的话题 38 | MyPage> selectByUserId(Integer userId, Integer pageNo, Integer pageSize); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/ICommentService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.Comment; 4 | import co.yiiu.pybbs.model.Topic; 5 | import co.yiiu.pybbs.model.User; 6 | import co.yiiu.pybbs.model.vo.CommentsByTopic; 7 | import co.yiiu.pybbs.util.MyPage; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * Created by tomoya. 14 | * Copyright (c) 2018, All Rights Reserved. 15 | * https://atjiu.github.io 16 | */ 17 | public interface ICommentService { 18 | // 根据话题id查询评论 19 | List selectByTopicId(Integer topicId); 20 | 21 | // 删除话题时删除相关的评论 22 | void deleteByTopicId(Integer topicId); 23 | 24 | // 根据用户id删除评论记录 25 | void deleteByUserId(Integer userId); 26 | 27 | // 保存评论 28 | Comment insert(Comment comment, Topic topic, User user); 29 | 30 | Comment selectById(Integer id); 31 | 32 | Comment selectByTgMessageId(Integer messageId); 33 | 34 | // 更新评论 35 | void update(Comment comment); 36 | 37 | // 对评论点赞 38 | int vote(Comment comment, User user); 39 | 40 | // 删除评论 41 | void delete(Comment comment); 42 | 43 | // 查询用户的评论 44 | MyPage> selectByUserId(Integer userId, Integer pageNo, Integer pageSize); 45 | 46 | MyPage> selectAllForAdmin(Integer pageNo, String startDate, String endDate, String username); 47 | 48 | // 查询今天新增的话题数 49 | int countToday(); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/IIndexedService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | /** 4 | * Created by tomoya. 5 | * Copyright (c) 2018, All Rights Reserved. 6 | * https://atjiu.github.io 7 | */ 8 | public interface IIndexedService { 9 | 10 | // 索引全部话题 11 | void indexAllTopic(); 12 | 13 | // 索引话题 14 | void indexTopic(String id, String title, String content); 15 | 16 | // 删除话题索引 17 | void deleteTopicIndex(String id); 18 | 19 | // 删除所有话题索引 20 | void batchDeleteIndex(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/INotificationService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | public interface INotificationService { 12 | // 查询消息 13 | List> selectByUserId(Integer userId, Boolean read, Integer limit); 14 | 15 | void markRead(Integer userId); 16 | 17 | // 查询未读消息数量 18 | long countNotRead(Integer userId); 19 | 20 | void deleteByTopicId(Integer topicId); 21 | 22 | void deleteByUserId(Integer userId); 23 | 24 | void insert(Integer userId, Integer targetUserId, Integer topicId, String action, String content); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/IOAuthUserService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.OAuthUser; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by tomoya. 9 | * Copyright (c) 2018, All Rights Reserved. 10 | * https://atjiu.github.io 11 | */ 12 | public interface IOAuthUserService { 13 | OAuthUser selectByTypeAndLogin(String type, String login); 14 | 15 | List selectByUserId(Integer userId); 16 | 17 | void addOAuthUser(Integer oauthId, String type, String login, String accessToken, String bio, String email, Integer 18 | userId, String refreshToken, String unionId, String openId); 19 | 20 | void update(OAuthUser oAuthUser); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/IPermissionService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.Permission; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2018, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | public interface IPermissionService { 14 | 15 | // 更新角色关联的权限时调用一下,清除缓存,让权限实时生效 16 | void clearRolePermissionCache(); 17 | 18 | // 根据角色id查询所有的权限 19 | List selectByRoleId(Integer roleId); 20 | 21 | // 根据父节点查询子节点 22 | List selectByPid(Integer pid); 23 | 24 | Map> selectAll(); 25 | 26 | Permission insert(Permission permission); 27 | 28 | Permission update(Permission permission); 29 | 30 | void delete(Integer id); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/IRolePermissionService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.RolePermission; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by tomoya. 9 | * Copyright (c) 2018, All Rights Reserved. 10 | * https://atjiu.github.io 11 | */ 12 | public interface IRolePermissionService { 13 | // 根据角色id查询所有的角色权限关联记录 14 | List selectByRoleId(Integer roleId); 15 | 16 | // 根据角色id删除关联关系 17 | void deleteByRoleId(Integer roleId); 18 | 19 | // 根据权限id删除关联关系 20 | void deleteByPermissionId(Integer permissionId); 21 | 22 | void insert(RolePermission rolePermission); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/IRoleService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.Role; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by tomoya. 9 | * Copyright (c) 2018, All Rights Reserved. 10 | * https://atjiu.github.io 11 | */ 12 | public interface IRoleService { 13 | Role selectById(Integer roleId); 14 | 15 | List selectAll(); 16 | 17 | void insert(String name, Integer[] permissionIds); 18 | 19 | void update(Integer id, String name, Integer[] permissionIds); 20 | 21 | void delete(Integer id); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/ISensitiveWordService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.SensitiveWord; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2018, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | public interface ISensitiveWordService { 14 | void save(SensitiveWord sensitiveWord); 15 | 16 | void update(SensitiveWord sensitiveWord); 17 | 18 | List selectAll(); 19 | 20 | void deleteById(Integer id); 21 | 22 | IPage page(Integer pageNo, String word); 23 | 24 | void updateWordById(Integer id, String word); 25 | 26 | SensitiveWord selectByWord(String word); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/ISystemConfigService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.SystemConfig; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2018, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | public interface ISystemConfigService { 14 | Map selectAllConfig(); 15 | 16 | // 根据键取值 17 | SystemConfig selectByKey(String key); 18 | 19 | Map selectAll(); 20 | 21 | // 在更新系统设置后,清一下selectAllConfig()的缓存 22 | void update(List> list); 23 | 24 | // 根据key更新数据 25 | void updateByKey(String key, SystemConfig systemConfig); 26 | 27 | Map selectAllConfigWithoutPassword(); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/ITagService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.Tag; 4 | import co.yiiu.pybbs.util.MyPage; 5 | import com.baomidou.mybatisplus.core.metadata.IPage; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * Created by tomoya. 12 | * Copyright (c) 2018, All Rights Reserved. 13 | * https://atjiu.github.io 14 | */ 15 | public interface ITagService { 16 | void selectTagsByTopicId(MyPage> page); 17 | 18 | Tag selectById(Integer id); 19 | 20 | Tag selectByName(String name); 21 | 22 | List selectByIds(List ids); 23 | 24 | // 根据话题查询关联的所有标签 25 | List selectByTopicId(Integer topicId); 26 | 27 | // 将创建话题时填的tag处理并保存 28 | List insertTag(String newTags); 29 | 30 | // 将标签的话题数都-1 31 | void reduceTopicCount(Integer id); 32 | 33 | // 查询标签关联的话题 34 | MyPage> selectTopicByTagId(Integer tagId, Integer pageNo); 35 | 36 | // 查询标签列表 37 | IPage selectAll(Integer pageNo, Integer pageSize, String name); 38 | 39 | void update(Tag tag); 40 | 41 | // 如果 topic_tag 表里还有关联的数据,这里删除会报错 42 | void delete(Integer id); 43 | 44 | //同步标签的话题数 45 | void async(); 46 | 47 | // 查询今天新增的标签数 48 | int countToday(); 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/ITopicService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.Topic; 4 | import co.yiiu.pybbs.model.User; 5 | import co.yiiu.pybbs.util.MyPage; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * Created by tomoya. 12 | * Copyright (c) 2018, All Rights Reserved. 13 | * https://atjiu.github.io 14 | */ 15 | public interface ITopicService { 16 | // 搜索 17 | MyPage> search(Integer pageNo, Integer pageSize, String keyword); 18 | 19 | // 分页查询话题 20 | MyPage> selectAll(Integer pageNo, String tab); 21 | 22 | // 查询话题作者其它的话题 23 | List selectAuthorOtherTopic(Integer userId, Integer topicId, Integer limit); 24 | 25 | // 查询用户的话题 26 | MyPage> selectByUserId(Integer userId, Integer pageNo, Integer pageSize); 27 | 28 | // 保存话题 29 | Topic insert(String title, String content, String tags, User user); 30 | 31 | // 根据id查询话题 32 | Topic selectById(Integer id); 33 | 34 | // 根据title查询话题,防止重复话题 35 | Topic selectByTitle(String title); 36 | 37 | // 处理话题的访问量 38 | Topic updateViewCount(Topic topic, String ip); 39 | 40 | // 更新话题 41 | void update(Topic topic, String tags); 42 | 43 | // 删除话题 44 | void delete(Topic topic); 45 | 46 | // 根据用户id删除帖子 47 | void deleteByUserId(Integer userId); 48 | 49 | MyPage> selectAllForAdmin(Integer pageNo, String startDate, String endDate, String username); 50 | 51 | // 查询今天新增的话题数 52 | int countToday(); 53 | 54 | int vote(Topic topic, User user); 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/ITopicTagService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.Tag; 4 | import co.yiiu.pybbs.model.TopicTag; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2018, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | public interface ITopicTagService { 14 | List selectByTopicId(Integer topicId); 15 | 16 | List selectByTagId(Integer tagId); 17 | 18 | void insertTopicTag(Integer topicId, List tagList); 19 | 20 | // 删除话题所有关联的标签记录 21 | void deleteByTopicId(Integer id); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/IUserService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service; 2 | 3 | import co.yiiu.pybbs.model.User; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2018, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | public interface IUserService { 14 | // 根据用户名查询用户,用于获取用户的信息比对密码 15 | User selectByUsername(String username); 16 | 17 | User addUser(String username, String password, String avatar, String email, String bio, String website, 18 | boolean needActiveEmail); 19 | 20 | // 通过手机号登录/注册创建用户 21 | User addUserWithMobile(String mobile); 22 | 23 | // 根据用户token查询用户 24 | User selectByToken(String token); 25 | 26 | // 根据用户mobile查询用户 27 | User selectByMobile(String mobile); 28 | 29 | // 根据用户email查询用户 30 | User selectByEmail(String email); 31 | 32 | User selectById(Integer id); 33 | 34 | User selectByIdWithoutCache(Integer id); 35 | 36 | // 查询用户积分榜 37 | List selectTop(Integer limit); 38 | 39 | // 更新用户信息 40 | void update(User user); 41 | 42 | IPage selectAll(Integer pageNo, String username); 43 | 44 | User selectByIdNoCatch(Integer id); 45 | 46 | // 查询今天新增的话题数 47 | int countToday(); 48 | 49 | // 删除用户 50 | void deleteUser(Integer id); 51 | 52 | // 删除redis缓存 53 | void delRedisUser(User user); 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/service/impl/IndexedService.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.service.impl; 2 | 3 | import co.yiiu.pybbs.service.IIndexedService; 4 | import org.springframework.stereotype.Service; 5 | 6 | /** 7 | * Created by tomoya. 8 | * Copyright (c) 2018, All Rights Reserved. 9 | * https://atjiu.github.io 10 | */ 11 | @Service 12 | public class IndexedService implements IIndexedService { 13 | 14 | // 索引全部话题 15 | @Override 16 | public void indexAllTopic() { 17 | } 18 | 19 | // 索引话题 20 | @Override 21 | public void indexTopic(String id, String title, String content) { 22 | } 23 | 24 | // 删除话题索引 25 | @Override 26 | public void deleteTopicIndex(String id) { 27 | } 28 | 29 | // 删除所有话题索引 30 | @Override 31 | public void batchDeleteIndex() { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/Constants.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util; 2 | 3 | /** 4 | * Created by tomoya. 5 | * Copyright (c) 2018, All Rights Reserved. 6 | * https://atjiu.github.io 7 | */ 8 | public class Constants { 9 | 10 | private Constants() { 11 | } 12 | 13 | public static final String REDIS_SYSTEM_CONFIG_KEY = "pybbs_system_config"; 14 | 15 | public static final String REDIS_TOPIC_KEY = "pybbs_topic_"; // 后面还要拼上话题的id 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/HttpUtil.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | import java.io.IOException; 6 | 7 | /** 8 | * Created by tomoya. 9 | * Copyright (c) 2018, All Rights Reserved. 10 | * https://atjiu.github.io 11 | */ 12 | public class HttpUtil { 13 | 14 | public static boolean isApiRequest(HttpServletRequest request) { 15 | return request.getHeader("Accept") == null || !request.getHeader("Accept").contains("text/html"); 16 | } 17 | 18 | // 根据请求接收的类型定义不同的响应方式 19 | // 判断请求对象request里的header里accept字段接收类型 20 | // 如果是 text/html 则响应一段js,这里要将response对象的响应内容类型也设置成 text/javascript 21 | // 如果是 application/json 则响应一串json,response 对象的响应内容类型要设置成 application/json 22 | // 因为响应内容描述是中文,所以都要带上 ;charset=utf-8 否则会有乱码 23 | // 写注释真累费劲。。 24 | public static void responseWrite(HttpServletRequest request, HttpServletResponse response) throws IOException { 25 | if (!HttpUtil.isApiRequest(request)) { 26 | response.setContentType("text/html;charset=utf-8"); 27 | response.sendRedirect("/login"); 28 | // response.getWriter().write(""); 29 | } else /*if (accept.contains("application/json"))*/ { 30 | response.setContentType("application/json;charset=utf-8"); 31 | Result result = new Result(); 32 | result.setCode(201); 33 | result.setDescription("请先登录"); 34 | response.getWriter().write(JsonUtil.objectToJson(result)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/IpUtil.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | public class IpUtil { 6 | public IpUtil() { 7 | } 8 | 9 | public static String getIpAddr(HttpServletRequest request) { 10 | String ip = request.getHeader("x-forwarded-for"); 11 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 12 | ip = request.getHeader("Proxy-Client-IP"); 13 | } 14 | 15 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 16 | ip = request.getHeader("WL-Proxy-Client-IP"); 17 | } 18 | 19 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 20 | ip = request.getRemoteAddr(); 21 | } 22 | 23 | return ip; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/LocaleMessageSourceUtil.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util; 2 | 3 | import org.springframework.context.MessageSource; 4 | import org.springframework.context.i18n.LocaleContextHolder; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.annotation.Resource; 8 | import java.util.Locale; 9 | 10 | /** 11 | * Created by tomoya. 12 | * Copyright (c) 2018, All Rights Reserved. 13 | * https://atjiu.github.io 14 | */ 15 | // 这个工具类代码来自 https://github.com/zl736732419/spring-boot-i18n/blob/master/src/main/java/com/zheng/utils 16 | // /LocaleMessageSourceUtil.java 17 | @Component 18 | public class LocaleMessageSourceUtil { 19 | 20 | @Resource 21 | private MessageSource messageSource; 22 | 23 | public String getMessage(String code) { 24 | return getMessage(code, null); 25 | } 26 | 27 | /** 28 | * @param code :对应messages配置的key. 29 | * @param args : 数组参数. 30 | * @return 31 | */ 32 | public String getMessage(String code, Object[] args) { 33 | return getMessage(code, args, ""); 34 | } 35 | 36 | /** 37 | * @param code :对应messages配置的key. 38 | * @param args : 数组参数. 39 | * @param defaultMessage : 没有设置key的时候的默认值. 40 | * @return 41 | */ 42 | public String getMessage(String code, Object[] args, String defaultMessage) { 43 | //这里使用比较方便的方法,不依赖request. 44 | Locale locale = LocaleContextHolder.getLocale(); 45 | return messageSource.getMessage(code, args, defaultMessage, locale); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/MarkdownUtil.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util; 2 | 3 | import org.commonmark.Extension; 4 | import org.commonmark.ext.autolink.AutolinkExtension; 5 | import org.commonmark.ext.gfm.tables.TableBlock; 6 | import org.commonmark.ext.gfm.tables.TablesExtension; 7 | import org.commonmark.node.Image; 8 | import org.commonmark.node.Node; 9 | import org.commonmark.parser.Parser; 10 | import org.commonmark.renderer.html.AttributeProvider; 11 | import org.commonmark.renderer.html.HtmlRenderer; 12 | 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | /** 18 | * Created by tomoya. 19 | * Copyright (c) 2018, All Rights Reserved. 20 | * https://atjiu.github.io 21 | */ 22 | public class MarkdownUtil { 23 | 24 | public static String render(String content) { 25 | List extensions = Arrays.asList(AutolinkExtension.create(), TablesExtension.create()); 26 | 27 | Parser parser = Parser.builder().extensions(extensions).build(); 28 | // 回车一次就可以实现换行 29 | HtmlRenderer renderer = HtmlRenderer.builder().softbreak("
").attributeProviderFactory(context -> new 30 | MyAttributeProvider()).extensions(extensions).build(); 31 | Node document = parser.parse(content == null ? "" : content); 32 | return renderer.render(document); 33 | } 34 | 35 | static class MyAttributeProvider implements AttributeProvider { 36 | 37 | @Override 38 | public void setAttributes(Node node, String s, Map map) { 39 | // 给图片添加上类样式,类样式可以在css里自定义 40 | if (node instanceof Image) { 41 | map.put("class", "md-image"); 42 | } 43 | // 给表格添加上类样式,类样式可以在css里自定义 44 | if (node instanceof TableBlock) { 45 | map.put("class", "table table-bordered"); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/Message.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util; 2 | 3 | import java.io.Serializable; 4 | 5 | public class Message implements Serializable { 6 | 7 | private static final long serialVersionUID = 1244988167184789309L; 8 | 9 | private String type; 10 | private Object payload; 11 | 12 | public Message() { 13 | } 14 | 15 | public Message(String type, Object payload) { 16 | this.type = type; 17 | this.payload = payload; 18 | } 19 | 20 | public String getType() { 21 | return type; 22 | } 23 | 24 | public void setType(String type) { 25 | this.type = type; 26 | } 27 | 28 | public Object getPayload() { 29 | return payload; 30 | } 31 | 32 | public void setPayload(Object payload) { 33 | this.payload = payload; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/Result.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util; 2 | 3 | /** 4 | * Created by tomoya. 5 | * Copyright (c) 2018, All Rights Reserved. 6 | * https://atjiu.github.io 7 | */ 8 | public class Result { 9 | 10 | private Integer code; 11 | private String description; 12 | private Object detail; 13 | 14 | public Integer getCode() { 15 | return code; 16 | } 17 | 18 | public void setCode(Integer code) { 19 | this.code = code; 20 | } 21 | 22 | public String getDescription() { 23 | return description; 24 | } 25 | 26 | public void setDescription(String description) { 27 | this.description = description; 28 | } 29 | 30 | public Object getDetail() { 31 | return detail; 32 | } 33 | 34 | public void setDetail(Object detail) { 35 | this.detail = detail; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/SecurityUtil.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util; 2 | 3 | 4 | import org.apache.commons.text.StringEscapeUtils; 5 | 6 | public class SecurityUtil { 7 | 8 | public static String sanitizeInput(String input) { 9 | return StringEscapeUtils.escapeHtml3(StringEscapeUtils.escapeHtml4(StringEscapeUtils.escapeEcmaScript(input))); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/captcha/Randoms.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util.captcha; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | *

随机工具类

7 | * 8 | * @author: wuhongjun 9 | * @version:1.0 10 | */ 11 | public class Randoms { 12 | private static final Random RANDOM = new Random(); 13 | //定义验证码字符.去除了O和I等容易混淆的字母 14 | public static final char ALPHA[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'G', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 15 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'p', 'q', 16 | 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '2', '3', '4', '5', '6', '7', '8', '9'}; 17 | 18 | /** 19 | * 产生两个数之间的随机数 20 | * 21 | * @param min 小数 22 | * @param max 比min大的数 23 | * @return int 随机数字 24 | */ 25 | public static int num(int min, int max) { 26 | return min + RANDOM.nextInt(max - min); 27 | } 28 | 29 | /** 30 | * 产生0--num的随机数,不包括num 31 | * 32 | * @param num 数字 33 | * @return int 随机数字 34 | */ 35 | public static int num(int num) { 36 | return RANDOM.nextInt(num); 37 | } 38 | 39 | public static char alpha() { 40 | return ALPHA[num(0, ALPHA.length)]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/captcha/Streams.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util.captcha; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | 7 | public class Streams { 8 | /** 9 | * 关闭输入流 10 | * 11 | * @param in 输入流 12 | */ 13 | public static void close(InputStream in) { 14 | if (in != null) { 15 | try { 16 | in.close(); 17 | } catch (IOException ioex) { 18 | // ignore 19 | } 20 | } 21 | } 22 | 23 | /** 24 | * 关闭输出流 25 | * 26 | * @param out 输出流 27 | */ 28 | public static void close(OutputStream out) { 29 | if (out != null) { 30 | try { 31 | out.flush(); 32 | } catch (IOException ioex) { 33 | // ignore 34 | } 35 | try { 36 | out.close(); 37 | } catch (IOException ioex) { 38 | // ignore 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/captcha/package-info.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util.captcha; 2 | 3 | /** 4 | * 这个包下的类是从网上找的,感谢大会们分享 5 | * 地址: https://blog.csdn.net/qq_34875064/article/details/51312127 6 | */ -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/identicon/generator/IBaseGenerator.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util.identicon.generator; 2 | 3 | import java.awt.*; 4 | 5 | /** 6 | * Author: Bryant Hang 7 | * Date: 15/1/10 8 | * Time: 下午2:43 9 | */ 10 | public interface IBaseGenerator { 11 | /** 12 | * 将hash字符串转换为bool二维6*5数组 13 | * 14 | * @param hash 15 | * @return 16 | */ 17 | public boolean[][] getBooleanValueArray(String hash); 18 | 19 | 20 | /** 21 | * 获取图片背景色 22 | * 23 | * @return 24 | */ 25 | public Color getBackgroundColor(); 26 | 27 | 28 | /** 29 | * 获取图案前景色 30 | * 31 | * @return 32 | */ 33 | public Color getForegroundColor(); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/co/yiiu/pybbs/util/identicon/generator/impl/MyGenerator.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util.identicon.generator.impl; 2 | 3 | import co.yiiu.pybbs.util.identicon.generator.IBaseGenerator; 4 | 5 | import java.awt.*; 6 | import java.util.Random; 7 | 8 | /** 9 | * Created by tomoya. 10 | * Copyright (c) 2016, All Rights Reserved. 11 | * https://atjiu.github.io 12 | */ 13 | public class MyGenerator implements IBaseGenerator { 14 | 15 | private Random random = new Random(); 16 | 17 | @Override 18 | public boolean[][] getBooleanValueArray(String hash) { 19 | 20 | boolean[][] array = new boolean[5][5]; 21 | 22 | //初始化字符串 23 | for (int i = 0; i < 5; i++) { 24 | for (int j = 0; j < 5; j++) { 25 | array[i][j] = false; 26 | } 27 | } 28 | 29 | for (int i = 0; i < 15; i++) { 30 | if (i % 3 == 0) { 31 | boolean b = random.nextBoolean(); 32 | array[i / 3][0] = b; 33 | array[i / 3][4] = b; 34 | } else if (i % 3 == 1) { 35 | boolean b = random.nextBoolean(); 36 | array[i / 3][1] = b; 37 | array[i / 3][3] = b; 38 | } else { 39 | boolean b = random.nextBoolean(); 40 | array[i / 3][2] = b; 41 | } 42 | } 43 | 44 | return array; 45 | } 46 | 47 | @Override 48 | public Color getBackgroundColor() { 49 | return new Color(236, 236, 236); 50 | } 51 | 52 | @Override 53 | public Color getForegroundColor() { 54 | int r = random.nextInt(256); 55 | int g = random.nextInt(256); 56 | int b = random.nextInt(256); 57 | return new Color(r, g, b); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | site: 2 | datasource_driver: com.mysql.cj.jdbc.Driver 3 | datasource_url: jdbc:mysql://localhost:3306/pybbs?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai 4 | datasource_username: root 5 | datasource_password: 123123 6 | 7 | # 想看mybatis执行的sql的时候,去掉下面这段的注释 8 | #mybatis-plus: 9 | # configuration: 10 | # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl -------------------------------------------------------------------------------- /src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | site: 2 | datasource_driver: com.mysql.cj.jdbc.Driver 3 | datasource_url: jdbc:mysql://mysql:3306/pybbs?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai 4 | datasource_username: root 5 | datasource_password: root # 这个密码跟 docker-compose.yml 里的 MYSQL_ROOT_PASSWORD 密码是一样的,不能为空 6 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | banner: 3 | location: file:./banner.txt 4 | 5 | site: 6 | datasource_driver: com.mysql.cj.jdbc.Driver 7 | datasource_url: jdbc:mysql://localhost:3306/pybbs?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai 8 | datasource_username: root 9 | datasource_password: 10 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | spring: 4 | profiles: 5 | active: dev 6 | banner: 7 | location: ["classpath:banner.txt", "file:./banner.txt"] 8 | freemarker: 9 | suffix: .ftl 10 | cache: false 11 | settings: 12 | template_exception_handler: rethrow 13 | number_format: 0.## 14 | template-loader-path: ["classpath:/templates/", "file:./templates/"] 15 | messages: 16 | basename: i18n/message 17 | servlet: 18 | multipart: 19 | max-file-size: -1 20 | max-request-size: -1 21 | 22 | 23 | # resources: 24 | # static-locations: ["classpath:static/", "file:./static/"] 25 | 26 | # 配置mapper.xml位置,这个位置可以在pom.xml里配置 27 | mybatis-plus: 28 | mapper-locations: classpath:/*Mapper.xml 29 | global-config: 30 | banner: false 31 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ________ ___ ___ ________ ________ ________ 2 | |\ __ \|\ \ / /|\ __ \|\ __ \|\ ____\ 3 | \ \ \|\ \ \ \/ / | \ \|\ /\ \ \|\ /\ \ \___|_ 4 | \ \ ____\ \ / / \ \ __ \ \ __ \ \_____ \ 5 | \ \ \___|\/ / / \ \ \|\ \ \ \|\ \|____|\ \ 6 | \ \__\ __/ / / \ \_______\ \_______\____\_\ \ 7 | \|__||\___/ / \|_______|\|_______|\_________\ 8 | \|___|/ \|_________| 9 | ========================================================= 10 | :: Spring Boot :: (v${spring-boot.version}) 11 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.10__delete_system_config_search.sql: -------------------------------------------------------------------------------- 1 | delete 2 | from system_config 3 | where id = 39; 4 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.11__update_system_config_key.sql: -------------------------------------------------------------------------------- 1 | UPDATE `system_config` 2 | SET `key` = 'upload_image_size_limit' 3 | WHERE `id` = '20'; 4 | 5 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 6 | VALUES (59, 'upload_video_size_limit', '20', '上传视频文件大小,单位MB,默认20MB', '25', 'number', NULL, '0'); 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.12__add_indexes.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `comment` ADD INDEX `comment_in_time` (`in_time`); 2 | ALTER TABLE `permission` ADD INDEX `permission_pid` (`pid`); 3 | ALTER TABLE `sensitive_word` ADD INDEX `sensitive_word` (`word`); 4 | ALTER TABLE `system_config` ADD INDEX `system_config_key` (`key`); 5 | ALTER TABLE `tag` ADD INDEX `tag_in_time` (`in_time`); 6 | ALTER TABLE `topic` ADD INDEX `topic_in_time` (`in_time`); 7 | ALTER TABLE `user` ADD INDEX `user_email` (`email`); 8 | ALTER TABLE `user` ADD INDEX `user_in_time` (`in_time`); 9 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.13__add_social_config.sql: -------------------------------------------------------------------------------- 1 | 2 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 3 | VALUES 4 | (60, NULL, NULL, 'Gitee配置', 0, '', NULL, 0), 5 | (61, 'oauth_gitee_client_id', '', 'Gitee登录配置项AppId', 60, 'text', NULL, 0), 6 | (62, 'oauth_gitee_client_secret', '', 'Gitee登录配置项AppSecret', 60, 'password', NULL, 0), 7 | (63, 'oauth_gitee_callback_url', '', 'Gitee登录配置项回调地址', 60, 'url', NULL, 0), 8 | 9 | (64, NULL, NULL, '新浪微博配置', 0, '', NULL, 0), 10 | (65, 'oauth_weibo_client_id', '', '新浪微博登录配置项AppId', 64, 'text', NULL, 0), 11 | (66, 'oauth_weibo_client_secret', '', '新浪微博登录配置项AppSecret', 64, 'password', NULL, 0), 12 | (67, 'oauth_weibo_callback_url', '', '新浪微博登录配置项回调地址', 64, 'url', NULL, 0), 13 | 14 | (68, NULL, NULL, '开源中国配置', 0, '', NULL, 0), 15 | (69, 'oauth_oschina_client_id', '', '开源中国登录配置项AppId', 68, 'text', NULL, 0), 16 | (70, 'oauth_oschina_client_secret', '', '开源中国登录配置项AppSecret', 68, 'password', NULL, 0), 17 | (71, 'oauth_oschina_callback_url', '', '开源中国登录配置项回调地址', 68, 'url', NULL, 0); -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.14__add_system_config_user_need_active.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `system_config` (`key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 2 | VALUES ('user_need_active', '1', '新注册用户需要激活', 23, 'radio', null, 0); 3 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.15__modify_content_character.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE topic 2 | MODIFY COLUMN title varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT null; 3 | ALTER TABLE topic 4 | MODIFY COLUMN content longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT null; 5 | ALTER TABLE comment 6 | MODIFY COLUMN content longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT null; -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.16__add_column_content_style.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `topic` 2 | ADD COLUMN `style` VARCHAR(50) NULL DEFAULT 'MD' AFTER `user_id`; 3 | 4 | ALTER TABLE `comment` 5 | ADD COLUMN `style` VARCHAR(50) NULL DEFAULT 'MD' AFTER `id`; 6 | 7 | UPDATE `topic` 8 | set `style` = "MD" 9 | where `style` is null; 10 | 11 | UPDATE `comment` 12 | set `style` = "MD" 13 | where `style` is null; 14 | 15 | INSERT INTO `system_config` (`key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 16 | VALUES ('content_style', 'MD', '发帖或者回复的输入框语法风格', '23', 'select', 'RICH,MD', '1'); -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.17__add_cloud_storage.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 2 | VALUES (75, NULL, '', '云存储', 0, NULL, NULL, 0); 3 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 4 | VALUES (76, 'cloud_storage_platform', 'LOCAL', '云存储平台', 75, 'select', 'LOCAL,QINIU,OSS', 0); 5 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 6 | VALUES (77, 'oss_key', '', '阿里云存储key', 75, 'text', NULL, 0); 7 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 8 | VALUES (78, 'oss_secret', '', '阿里云存储secret', 75, 'text', NULL, 0); 9 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 10 | VALUES (79, 'oss_bucket', '', '阿里云存储bucket', 75, 'text', NULL, 0); 11 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 12 | VALUES (80, 'oss_end_point', 'http://oss-cn-hangzhou.aliyuncs.com', '阿里云存储上传地址(请根据文档来设置)', 75, 'url', NULL, 0); 13 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 14 | VALUES (81, 'qiniu_key', '', '七牛云存储key', 75, 'text', NULL, 0); 15 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 16 | VALUES (82, 'qiniu_secret', '', '七牛云存储secret', 75, 'text', NULL, 0); 17 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 18 | VALUES (83, 'qiniu_bucket', '', '七牛云存储bucket', 75, 'text', NULL, 0); 19 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 20 | VALUES (84, 'qiniu_domain', '', '七牛云存储访问域名(最后没有/)', 75, 'url', NULL, 0); 21 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.18__add_tgbot.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `system_config`(`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 2 | VALUES (85, NULL, '', 'Telegram', 0, NULL, NULL, 0), 3 | (86, 'tg_webhook_secret_token', '', '回调鉴权密钥(webhook)', 85, 'password', NULL, 0), 4 | (87, 'tg_access_token', '', '机器人接口令牌', 85, 'password', NULL, 0), 5 | (88, 'tg_to_chat_id', '', 'TG接收消息用户ID', 85, 'text', NULL, 0), 6 | (89, 'comment_need_examine', '0', '评论是否需要审核', 23, 'radio', NULL, 0), 7 | (90, NULL, NULL, '系统代理', 0, NULL, NULL, 0), 8 | (91, 'http_proxy', '', '代理地址(如:127.0.0.1)', 90, 'text', NULL, 1), 9 | (92, 'http_proxy_port', '', '代理端口(如:1087)', 90, 'number', NULL, 1); 10 | 11 | ALTER TABLE `comment` 12 | ADD COLUMN `tg_message_id` int(11) NULL AFTER `up_ids`, 13 | ADD COLUMN `status` bit(1) DEFAULT b'0' AFTER `tg_message_id`; 14 | 15 | INSERT INTO `permission`(`id`, `name`, `value`, `pid`) 16 | VALUES (56, '评论审核', 'comment:examine', 3); 17 | 18 | INSERT INTO `role_permission`(`role_id`, `permission_id`) 19 | VALUES (1, 56), 20 | (2, 56); 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.1__user_add_field.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `user` ADD `active` BIT(1) NOT NULL DEFAULT b'0' AFTER `email_notification`; 2 | UPDATE `user` set `active` = TRUE WHERE `email` IS NOT NULL; 3 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.2__update_system_config_default_theme.sql: -------------------------------------------------------------------------------- 1 | UPDATE `system_config` set `value` = 'default' WHERE `key` = 'theme'; 2 | UPDATE `system_config` set `type` = 'password' WHERE `key` = 'oauth_github_client_secret'; 3 | UPDATE `system_config` set `description` = 'Github配置' WHERE `id` = 40; 4 | UPDATE `system_config` set `description` = 'WebSocket' WHERE `id` = 45; 5 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.3__delete_system_config_redis_ssl.sql: -------------------------------------------------------------------------------- 1 | delete from `system_config` where `key` = 'redis_ssl'; -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.4__alter_code.sql: -------------------------------------------------------------------------------- 1 | -- code表增加mobile字段 2 | 3 | ALTER TABLE `code` 4 | ALTER `user_id` DROP DEFAULT; 5 | ALTER TABLE `code` 6 | CHANGE COLUMN `user_id` `user_id` INT(11) NULL AFTER `id`, 7 | CHANGE COLUMN `email` `email` VARCHAR(255) NULL DEFAULT NULL AFTER `expire_time`, 8 | ADD COLUMN `mobile` VARCHAR(255) NULL DEFAULT NULL AFTER `email`; 9 | ALTER TABLE `user` 10 | ADD COLUMN `mobile` VARCHAR(255) NULL DEFAULT NULL AFTER `email`; 11 | ALTER TABLE `system_config` 12 | CHANGE COLUMN `type` `type` VARCHAR(255) NULL DEFAULT NULL; 13 | ALTER TABLE `system_config` 14 | CHANGE COLUMN `value` `value` VARCHAR(255) NULL DEFAULT ''; 15 | 16 | -- 添加阿里云短信配置 17 | 18 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) VALUES (49, NULL, NULL, '短信配置', 0, NULL, NULL, 0); 19 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) VALUES (50, 'sms_access_key_id', '', '创建短信连接的key', 49, 'text', NULL, 0); 20 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) VALUES (51, 'sms_secret', '', '创建短信连接的密钥', 49, 'password', NULL, 0); 21 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) VALUES (52, 'sms_sign_name', '', '短信签名,在阿里云申请的签名', 49, 'text', NULL, 0); 22 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) VALUES (53, 'sms_template_code', '', '短信模板Code,在阿里云申请的模板Code', 49, 'text', NULL, 0); 23 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.5__add_sensitive_word.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `sensitive_word` ( 2 | `id` INT(11) NOT NULL AUTO_INCREMENT, 3 | `word` VARCHAR(255) NOT NULL COMMENT '敏感词', 4 | PRIMARY KEY (`id`) 5 | ); -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.6__add_permission_about_sensitive_word.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `permission` (`id`, `name`, `value`, `pid`) 2 | VALUES 3 | (50, '敏感词', 'sensitive_word', 0), 4 | (51, '敏感词列表', 'sensitive_word:list', 50), 5 | (52, '敏感词添加', 'sensitive_word:add', 50), 6 | (53, '敏感词编辑', 'sensitive_word:edit', 50), 7 | (54, '敏感词删除', 'sensitive_word:delete', 50), 8 | (55, '敏感词导入', 'sensitive_word:import', 50); 9 | 10 | INSERT INTO `role_permission` (`role_id`, `permission_id`) 11 | VALUES 12 | (1, 51), 13 | (1, 52), 14 | (1, 53), 15 | (1, 54), 16 | (1, 55); 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.7__alter_oauth_user.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `oauth_user` CHANGE `oauth_id` `oauth_id` INT(11) NULL; 2 | 3 | ALTER TABLE `oauth_user` ADD `refresh_token` VARCHAR(255) NULL DEFAULT NULL AFTER `user_id`; 4 | ALTER TABLE `oauth_user` ADD `union_id` VARCHAR(255) NULL DEFAULT NULL AFTER `refresh_token`; 5 | ALTER TABLE `oauth_user` ADD `expires_in` VARCHAR(255) NULL DEFAULT NULL AFTER `union_id`; 6 | 7 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 8 | VALUES 9 | (54, NULL, NULL, 'WeChat配置', 0, '', NULL, 0), 10 | (55, 'oauth_wechat_client_id', '', 'WeChat登录配置项AppId', 54, 'text', NULL, 0), 11 | (56, 'oauth_wechat_client_secret', '', 'WeChat登录配置项AppSecret', 54, 'password', NULL, 0), 12 | (57, 'oauth_wechat_callback_url', '', 'WeChat登录配置项回调地址', 54, 'url', NULL, 0); 13 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.8__add_sms_region_id.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `system_config` (`id`, `key`, `value`, `description`, `pid`, `type`, `option`, `reboot`) 2 | VALUES 3 | (58, 'sms_region_id', '', '短信服务所在区域 例如: cn-hangzhou', 49, 'text', NULL, 0); 4 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1.9__update_system_config_ws.sql: -------------------------------------------------------------------------------- 1 | update system_config set `key` = 'websocket_url', description = "websocket服务的连接地址" where `key` = "websocket_host"; 2 | delete from system_config where `key` = "websocket_port"; 3 | -------------------------------------------------------------------------------- /src/main/resources/i18n/message.properties: -------------------------------------------------------------------------------- 1 | index = 首页 2 | tag = 标签 3 | search = 搜索 4 | login = 登录 5 | github_login = Github登录 6 | register = 注册 7 | notification = 通知 8 | setting = 设置 9 | logout = 登出 10 | forget_password = 忘记密码? 11 | 12 | welcome = 欢迎您 13 | 14 | admin.dashboard = 仪表盘 15 | admin.topics = 话题列表 16 | admin.comments = 评论列表 17 | admin.tags = 标签列表 18 | admin.sensitive_word = 敏感词列表 19 | admin.users = 用户列表 20 | admin.permission_config = 权限中心 21 | admin.admin_users = 后台用户列表 22 | admin.roles = 角色列表 23 | admin.permissions = 权限列表 24 | admin.system_config = 系统设置 25 | -------------------------------------------------------------------------------- /src/main/resources/i18n/message_en_US.properties: -------------------------------------------------------------------------------- 1 | index = Home 2 | tag = Tags 3 | search = Search 4 | login = Login 5 | github_login = Github OAuth 6 | register = Register 7 | notification = Notifications 8 | setting = Settings 9 | logout = Logout 10 | forget_password = Forgot password? 11 | 12 | welcome = Welcome 13 | 14 | admin.dashboard = Dashboard 15 | admin.topics = Topics 16 | admin.comments = Comments 17 | admin.tags = Tags 18 | admin.sensitive_word = Sensitive Word 19 | admin.users = Users 20 | admin.permission_config = Permission Config 21 | admin.admin_users = AdminUsers 22 | admin.roles = Roles 23 | admin.permissions = Permissions 24 | admin.system_config = SystemConfig 25 | -------------------------------------------------------------------------------- /src/main/resources/i18n/message_zh_CN.properties: -------------------------------------------------------------------------------- 1 | index = 首页 2 | tag = 标签 3 | search = 搜索 4 | login = 登录 5 | github_login = Github登录 6 | register = 注册 7 | notification = 通知 8 | setting = 设置 9 | logout = 登出 10 | forget_password = 忘记密码? 11 | 12 | welcome = 欢迎您 13 | 14 | admin.dashboard = 仪表盘 15 | admin.topics = 话题列表 16 | admin.comments = 评论列表 17 | admin.tags = 标签列表 18 | admin.sensitive_word = 敏感词列表 19 | admin.users = 用户列表 20 | admin.permission_config = 权限中心 21 | admin.admin_users = 后台用户列表 22 | admin.roles = 角色列表 23 | admin.permissions = 权限列表 24 | admin.system_config = 系统设置 25 | -------------------------------------------------------------------------------- /src/main/resources/static/theme/default/js/app.js: -------------------------------------------------------------------------------- 1 | function suc(msg) { 2 | layer.msg(msg, {icon: 1, anim: 5, offset: 't', time: 20000}); 3 | } 4 | 5 | function err(msg) { 6 | layer.msg(msg, {icon: 2, anim: 6, offset: 't', time: 20000}); 7 | } 8 | 9 | function tip(msg) { 10 | layer.msg(msg, {offset: 't'}); 11 | } 12 | 13 | function req(method, url, body, token, cb) { 14 | let setup = { 15 | cache: false, 16 | async: false, 17 | dataType: 'json', 18 | contentType: 'application/json' 19 | }; 20 | let _token = typeof arguments[2] === "string" ? arguments[2] 21 | : typeof arguments[3] === "string" ? arguments[3] 22 | : typeof arguments[4] === "string" ? arguments[4] : undefined; 23 | if (_token) { 24 | setup.headers = { 25 | 'token': _token 26 | } 27 | } 28 | $.ajaxSetup(setup); 29 | let reqObj; 30 | if (method.toUpperCase() === "GET" || method.toUpperCase() === "POST" || method.toUpperCase() === "PUT" || method.toUpperCase() === "DELETE") { 31 | if (typeof arguments[2] === "object") { 32 | let data = JSON.stringify(body); 33 | if (method.toUpperCase() === "GET") { 34 | data = $.param(body); 35 | } 36 | reqObj = $.ajax({url, method, data}); 37 | } else { 38 | reqObj = $.ajax({url, method}); 39 | } 40 | } else { 41 | throw new Error("request method not support!"); 42 | } 43 | let _cb = typeof arguments[2] === "function" ? arguments[2] 44 | : typeof arguments[3] === "function" ? arguments[3] 45 | : typeof arguments[4] === "function" ? arguments[4] : undefined; 46 | if (_cb) reqObj.done(_cb); 47 | reqObj.fail(err => console.error(err)); 48 | } -------------------------------------------------------------------------------- /src/main/resources/static/theme/default/libs/layer/theme/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/src/main/resources/static/theme/default/libs/layer/theme/default/icon-ext.png -------------------------------------------------------------------------------- /src/main/resources/static/theme/default/libs/layer/theme/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/src/main/resources/static/theme/default/libs/layer/theme/default/icon.png -------------------------------------------------------------------------------- /src/main/resources/static/theme/default/libs/layer/theme/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/src/main/resources/static/theme/default/libs/layer/theme/default/loading-0.gif -------------------------------------------------------------------------------- /src/main/resources/static/theme/default/libs/layer/theme/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/src/main/resources/static/theme/default/libs/layer/theme/default/loading-1.gif -------------------------------------------------------------------------------- /src/main/resources/static/theme/default/libs/layer/theme/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/src/main/resources/static/theme/default/libs/layer/theme/default/loading-2.gif -------------------------------------------------------------------------------- /src/main/resources/static/theme/default/libs/layer/theme/moon/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atjiu/pybbs/008d07eab6834634a502681fd6fdd328611dc795/src/main/resources/static/theme/default/libs/layer/theme/moon/default.png -------------------------------------------------------------------------------- /src/main/resources/templates/admin/layout/footer.ftl: -------------------------------------------------------------------------------- 1 |
2 | 5 | Copyright © 2014-2016 Almsaeed Studio. All rights 6 | reserved. 7 |
8 |
9 | -------------------------------------------------------------------------------- /src/main/resources/templates/admin/layout/header.ftl: -------------------------------------------------------------------------------- 1 |
2 | 3 | 7 | 8 | 18 |
19 | -------------------------------------------------------------------------------- /src/main/resources/templates/admin/layout/paginate.ftl: -------------------------------------------------------------------------------- 1 | <#macro paginate currentPage totalPage actionUrl urlParas="" showdivide="yes"> 2 | <#if (totalPage <= 0) || (currentPage > totalPage)><#return> 3 | <#local startPage = currentPage - 2> 4 | <#if (startPage < 1)><#local startPage = 1> 5 | 6 | <#local endPage = currentPage + 2> 7 | <#if (endPage > totalPage)><#local endPage = totalPage> 8 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/components/author.ftl: -------------------------------------------------------------------------------- 1 |
2 |
3 | 作者 4 |
5 |
6 |
7 | 8 | 9 | 10 |
11 | 14 |

积分:${topicUser.score}

15 |
16 |
17 |
18 | ${(topicUser.bio!"这家伙很懒,什么都没有留下")?html} 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/components/create_topic_guide.ftl: -------------------------------------------------------------------------------- 1 |
2 |
发帖提示
3 |
4 |

请在标题中描述内容要点。如果一件事情在标题的长度内就已经可以说清楚,那就没有必要写正文了。

5 |

可以在正文中为你要发布的主题添加更多细节。编辑器支持 Markdown 文本标记语法。

6 | 7 |
8 |
9 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/components/github_repos.ftl: -------------------------------------------------------------------------------- 1 |
2 |
${user.username}'s repos on GitHub
3 |
    4 |
    5 | 22 | 23 | 36 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/components/markdown_guide.ftl: -------------------------------------------------------------------------------- 1 |
    2 |
    Markdown 语法参考
    3 |
    4 |

    ## 标题

    5 |

    `System.out.println('行内代码')`

    6 |

    ```java\n code \n``` 标记代码块

    7 |

    [内容](链接)

    8 |

    ![](图片链接)

    9 |
    10 |
    11 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/components/notification.ftl: -------------------------------------------------------------------------------- 1 | <#macro notification userId read limit> 2 | <@tag_notifications userId=userId read=read limit=limit> 3 | <#list notifications as notification> 4 |
    5 | 6 |
    7 |
    style="font-weight:700;"> 8 | ${notification.username} 9 | ${model.formatDate(notification.inTime)} 10 | <#if notification.action == "COMMENT"> 11 | 评论了你的话题 ${notification.title} 12 | <#elseif notification.action == "REPLY"> 13 | 在话题 ${notification.title} 下回复了你 14 | <#elseif notification.action == "COLLECT"> 15 | 收藏了你的话题 ${notification.title} 16 | 17 |
    18 | <#if notification.content??> 19 |
    20 | ${model.formatContent(notification.content)} 21 |
    22 | 23 |
    24 |
    25 | <#if notification_has_next> 26 |
    27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/components/other_topic.ftl: -------------------------------------------------------------------------------- 1 | <#macro other_topic userId topicId limit> 2 |
    3 |
    作者其它话题
    4 | 5 | <@tag_other_topic userId=userId topicId=topicId limit=limit> 6 | <#list topics as topic> 7 | 8 | 9 | 10 | 11 | 12 |
    ${topic.title}
    13 |
    14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/components/score.ftl: -------------------------------------------------------------------------------- 1 | <#macro score limit top100=false> 2 |
    3 |
    4 | 积分榜 5 | <#if !top100> 6 | Top100 7 | 8 |
    9 | 10 | <#if top100> 11 | 12 | 13 | 14 | 15 | 16 | <@tag_score limit=limit> 17 | <#list users as user> 18 | 19 | 20 | 21 | 22 | 23 | 24 |
    用户名积分
    ${user.username}${user.score}
    25 |
    26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/components/token.ftl: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 用户Token 4 | 刷新Token 5 |
    6 |
    7 |

    Token:${_user.token!}

    8 |
    9 |
    10 |
    11 | 12 | 37 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/components/user_info.ftl: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 | 7 |
    8 |
    9 | ${_user.username!} 10 |
    11 | ${(_user.bio!"这家伙很懒,什么都没有留下")?html} 12 |
    13 |
    14 |
    15 |
    16 | 21 |
    22 | 28 |
    29 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/components/welcome.ftl: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | ${site.intro!} 4 |
    5 |
    6 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/error.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Ooooops, 出错了~~ 9 | 10 | 11 | 12 |
    13 |
    14 |
    15 |
    16 |
    17 |

    : (

    18 |

    ${errorCode}

    19 |

    ${exception.message!}

    20 |
    21 |
    22 |
    23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/layout/footer.ftl: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/layout/layout.ftl: -------------------------------------------------------------------------------- 1 | <#macro html page_title page_tab> 2 | 3 | 4 | 5 | 6 | 8 | 9 | ${page_title!} 10 | 11 | <#--css--> 12 | 14 | 16 | 17 | <#--javascript--> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
    26 | <#include "header.ftl"/> 27 | <@header page_tab=page_tab/> 28 |
    29 | <#nested /> 30 |
    31 |
    32 | <#include "footer.ftl"/> 33 | <#include "../components/websocket.ftl"/> 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/search.ftl: -------------------------------------------------------------------------------- 1 | <#include "layout/layout.ftl"/> 2 | <@html page_title="首页" page_tab="index"> 3 |
    4 |
    5 |
    6 |
    搜索结果
    7 | <@tag_search pageNo=pageNo keyword=keyword> 8 | 9 | <#list page.records as map> 10 | 11 | 13 | 14 | 15 |
    ${map.title!}
    16 | <#include "components/paginate.ftl"/> 17 | <@paginate currentPage=page.current totalPage=page.pages actionUrl="/search" urlParas="&keyword=${keyword!}"/> 18 | 19 |
    20 |
    21 |
    22 | <#if _user??> 23 | <#include "components/user_info.ftl"/> 24 | <#else> 25 | <#include "components/welcome.ftl"/> 26 | 27 |
    28 |
    29 | 30 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/tag/tag.ftl: -------------------------------------------------------------------------------- 1 | <#include "../layout/layout.ftl"/> 2 | <@html page_title="标签" page_tab=""> 3 |
    4 |
    5 |
    6 |
    7 |

    8 | <#if tag.icon??> 9 | 10 | 11 | ${tag.name} 12 | 共有${tag.topicCount!0}篇话题 13 | <#if _user??> 14 | 发布话题 15 | 16 |

    17 | ${tag.description!} 18 | 19 |
    20 |
    21 |
    22 | <#include "../components/topics.ftl"/> 23 | <@topics page=page tags=false/> 24 | 25 | <#include "../components/paginate.ftl"/> 26 | <@paginate currentPage=page.current totalPage=page.pages actionUrl="/topic/tag/${tag.name}"/> 27 |
    28 |
    29 |
    30 | 37 |
    38 | 39 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/tag/tags.ftl: -------------------------------------------------------------------------------- 1 | <#include "../layout/layout.ftl"/> 2 | <@html page_title="标签" page_tab="tags"> 3 |
    4 |
    5 |
    6 |
    7 | <@tag_tags pageNo=pageNo pageSize=40> 8 |
    9 | <#list page.records as tag> 10 |
    11 | <#if tag.icon??> 12 | 13 | 14 | 15 | ${tag.name} 16 | 17 | x ${tag.topicCount} 18 | ${tag.description!} 19 |
    20 | 21 |
    22 | <#include "../components/paginate.ftl"/> 23 | <@paginate currentPage=page.current totalPage=page.pages actionUrl="/tags"/> 24 | 25 |
    26 |
    27 |
    28 | 35 |
    36 | 37 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/top100.ftl: -------------------------------------------------------------------------------- 1 | <#include "layout/layout.ftl"/> 2 | <@html page_title="Top100" page_tab=""> 3 |
    4 |
    5 | <#include "components/score.ftl"/> 6 | <@score limit=100 top100=true/> 7 |
    8 |
    9 | <#if _user??> 10 | <#include "components/user_info.ftl"/> 11 | <#else> 12 | <#include "components/welcome.ftl"/> 13 | 14 |
    15 |
    16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/user/collects.ftl: -------------------------------------------------------------------------------- 1 | <#include "../layout/layout.ftl"/> 2 | <@html page_title="${username}收藏的话题" page_tab=""> 3 |
    4 |
    5 |
    6 |
    ${username}评论的话题
    7 |
    8 | <@tag_user_collects pageNo=pageNo pageSize=site.page_size username=username> 9 | <#if collects.total == 0> 10 | 暂无评论 11 | <#else> 12 | <#include "../components/topics.ftl"/> 13 | <@topics page=collects/> 14 | 15 | 16 |
    17 |
    18 |
    19 | 28 |
    29 | 30 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/user/comments.ftl: -------------------------------------------------------------------------------- 1 | <#include "../layout/layout.ftl"/> 2 | <@html page_title="${username}评论的话题" page_tab=""> 3 |
    4 |
    5 | <#include "../components/user_comments.ftl"/> 6 | <@user_comments pageNo=pageNo pageSize=site.page_size username=username isPaginate=true/> 7 |
    8 | 17 |
    18 | 19 | -------------------------------------------------------------------------------- /src/main/resources/templates/theme/default/user/topics.ftl: -------------------------------------------------------------------------------- 1 | <#include "../layout/layout.ftl"/> 2 | <@html page_title="${username}创建的话题" page_tab=""> 3 |
    4 |
    5 | <#include "../components/user_topics.ftl"/> 6 | <@user_topics pageNo=pageNo pageSize=site.page_size username=username isPaginate=true/> 7 |
    8 | 17 |
    18 | 19 | -------------------------------------------------------------------------------- /src/test/java/co/yiiu/pybbs/PybbsApplicationTests.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs; 2 | 3 | import co.yiiu.pybbs.plugin.ElasticSearchService; 4 | import co.yiiu.pybbs.util.MyPage; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.Map; 12 | 13 | @RunWith(SpringRunner.class) 14 | @SpringBootTest 15 | public class PybbsApplicationTests { 16 | 17 | @Resource 18 | ElasticSearchService elasticSearchService; 19 | 20 | @Test 21 | public void contextLoads() { 22 | MyPage> mapMyPage = elasticSearchService.searchDocument(1, 20, "你好", "title"); 23 | for (Map record : mapMyPage.getRecords()) { 24 | System.out.println(record.toString()); 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/co/yiiu/pybbs/util/StringUtilTest.java: -------------------------------------------------------------------------------- 1 | package co.yiiu.pybbs.util; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * Created by tomoya at 2019/5/24 10 | */ 11 | public class StringUtilTest { 12 | 13 | @Test 14 | public void check() { 15 | String url = "https://www.97560.com/topic/538825475611426816/?aa=dasd&asd=123123123"; 16 | String atRegex = "/topic/(\\d+)"; 17 | Pattern regex = Pattern.compile(atRegex); 18 | Matcher regexMatcher = regex.matcher(url); 19 | while (regexMatcher.find()) { 20 | System.out.println(regexMatcher.group(1)); 21 | } 22 | } 23 | 24 | @Test 25 | public void test() { 26 | String s = "PAYPALISHIRING"; 27 | convert(s, 3); 28 | } 29 | 30 | public void convert(String s, int numRows) { 31 | int x = s.length() / numRows + numRows; 32 | char[][] c = new char[x][numRows]; 33 | char[] schar = s.toCharArray(); 34 | int offset = 0; 35 | int index = s.length() - 1; 36 | for (int i = 0; i < c.length; i++) { 37 | for (int j = 0; j < c[i].length; j++) { 38 | if (offset > 0 && offset != j) { 39 | continue; 40 | } 41 | c[i][j] = schar[index]; 42 | index++; 43 | } 44 | offset++; 45 | if (offset == numRows - 1) offset = 0; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | java -jar pybbs.jar --spring.profiles.active=prod > log.file 2>&1 & 3 | --------------------------------------------------------------------------------