{
14 |
15 | public static final int MENU = 0;
16 | public static final int FUNCTION = 1;
17 |
18 | @TableId(type = IdType.AUTO)
19 | private Integer id;
20 |
21 | private String name;
22 |
23 | private String method;
24 |
25 | private String path;
26 |
27 | private String authority;
28 | }
29 |
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/entity/SysMenu.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.IdType;
4 | import com.baomidou.mybatisplus.annotation.TableId;
5 | import com.baomidou.mybatisplus.annotation.TableName;
6 | import com.baomidou.mybatisplus.extension.activerecord.Model;
7 | import lombok.Data;
8 | import lombok.EqualsAndHashCode;
9 |
10 | import java.time.LocalDateTime;
11 |
12 | /**
13 | * Note: sub-menu only appear when route children.length >= 1
14 | * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
15 | *
16 | * hidden: true if set true, item will not show in the sidebar(default is false)
17 | * alwaysShow: true if set true, will always show the root menu
18 | * if not set alwaysShow, when item has more than one children route,
19 | * it will becomes nested mode, otherwise not show the root menu
20 | * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
21 | * name:'router-name' the name is used by (must set!!!)
22 | * meta : {
23 | * roles: ['admin','editor'] control the page roles (you can set multiple roles)
24 | * title: 'title' the name show in sidebar and breadcrumb (recommend set)
25 | * icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
26 | * breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
27 | * activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
28 | * }
29 | */
30 | @TableName("sys_menu")
31 | @EqualsAndHashCode(callSuper = true)
32 | @Data
33 | public class SysMenu extends Model {
34 |
35 | public static final Integer MENU = 0;
36 | public static final Integer FUNCTION = 1;
37 |
38 | @TableId(type = IdType.AUTO)
39 | private Integer id;
40 |
41 | private String path;
42 |
43 | private String component;
44 |
45 | private Boolean hidden;
46 |
47 | private Boolean alwaysShow;
48 |
49 | private String redirect;
50 |
51 | private String name;
52 |
53 | private String title;
54 |
55 | private String authority;
56 |
57 | private String icon;
58 |
59 | private Boolean breadcrumb;
60 |
61 | private Boolean noCache;
62 |
63 | private Integer parentId;
64 |
65 | private Integer weight;
66 |
67 | private Integer type;
68 |
69 | private LocalDateTime createTime;
70 |
71 | private LocalDateTime updateTime;
72 | }
73 |
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/entity/SysMenuAuthority.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.IdType;
4 | import com.baomidou.mybatisplus.annotation.TableId;
5 | import com.baomidou.mybatisplus.annotation.TableName;
6 | import com.baomidou.mybatisplus.extension.activerecord.Model;
7 | import lombok.AllArgsConstructor;
8 | import lombok.Data;
9 | import lombok.EqualsAndHashCode;
10 | import lombok.NoArgsConstructor;
11 |
12 | import java.time.LocalDateTime;
13 |
14 | @Data
15 | @TableName("sys_menu_authority")
16 | @EqualsAndHashCode(callSuper = true)
17 | @AllArgsConstructor
18 | @NoArgsConstructor
19 | public class SysMenuAuthority extends Model {
20 |
21 | private static final long serialVersionUID = 1L;
22 |
23 | @TableId(type = IdType.AUTO)
24 | private Integer id;
25 |
26 | private Integer menuId;
27 |
28 | private Integer authorityId;
29 |
30 | private LocalDateTime createTime;
31 |
32 | private LocalDateTime updateTime;
33 | }
34 |
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/entity/SysRole.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.IdType;
4 | import com.baomidou.mybatisplus.annotation.TableId;
5 | import com.baomidou.mybatisplus.annotation.TableName;
6 | import com.baomidou.mybatisplus.extension.activerecord.Model;
7 | import lombok.Data;
8 | import lombok.EqualsAndHashCode;
9 |
10 | import java.time.LocalDateTime;
11 |
12 | @Data
13 | @TableName("sys_role")
14 | @EqualsAndHashCode(callSuper = true)
15 | public class SysRole extends Model {
16 | private static final long serialVersionUID = 1L;
17 |
18 | @TableId(type = IdType.AUTO)
19 | private Integer id;
20 |
21 | private String name;
22 |
23 | private String remark;
24 |
25 | private Boolean isLock = false;
26 |
27 | private LocalDateTime createTime;
28 |
29 | private LocalDateTime updateTime;
30 | }
31 |
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/entity/SysRoleMenu.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.IdType;
4 | import com.baomidou.mybatisplus.annotation.TableId;
5 | import com.baomidou.mybatisplus.annotation.TableName;
6 | import com.baomidou.mybatisplus.extension.activerecord.Model;
7 | import lombok.Data;
8 | import lombok.EqualsAndHashCode;
9 |
10 | import java.time.LocalDateTime;
11 |
12 | @Data
13 | @TableName("sys_role_menu")
14 | @EqualsAndHashCode(callSuper = true)
15 | public class SysRoleMenu extends Model {
16 |
17 | private static final long serialVersionUID = 1L;
18 |
19 | @TableId(type = IdType.AUTO)
20 | private Integer id;
21 |
22 | private Integer roleId;
23 |
24 | private Integer menuId;
25 |
26 | private LocalDateTime createTime;
27 |
28 | private LocalDateTime updateTime;
29 | }
30 |
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/entity/SysRoleUser.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.IdType;
4 | import com.baomidou.mybatisplus.annotation.TableId;
5 | import com.baomidou.mybatisplus.annotation.TableName;
6 | import com.baomidou.mybatisplus.extension.activerecord.Model;
7 | import lombok.Data;
8 | import lombok.EqualsAndHashCode;
9 |
10 | import java.time.LocalDateTime;
11 |
12 | @Data
13 | @TableName("sys_role_user")
14 | @EqualsAndHashCode(callSuper = true)
15 | public class SysRoleUser extends Model {
16 | private static final long serialVersionUID = 1L;
17 |
18 | @TableId(type = IdType.AUTO)
19 | private Integer id;
20 |
21 | private Integer roleId;
22 |
23 | private Integer userId;
24 |
25 | private LocalDateTime createTime;
26 |
27 | private LocalDateTime updateTime;
28 | }
29 |
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/mapper/SysAuthorityMapper.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.mapper;
2 |
3 | import cn.flizi.ext.rbac.entity.SysAuthority;
4 | import cn.flizi.ext.rbac.entity.SysMenuAuthority;
5 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
6 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
7 | import com.baomidou.mybatisplus.core.metadata.IPage;
8 | import com.baomidou.mybatisplus.core.toolkit.Constants;
9 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
10 | import org.apache.ibatis.annotations.Mapper;
11 | import org.apache.ibatis.annotations.Param;
12 | import org.apache.ibatis.annotations.Select;
13 |
14 | @Mapper
15 | public interface SysAuthorityMapper extends BaseMapper {
16 |
17 | @Select("select a.authority, a.name, a.path, a.method, ma.id from sys_menu_authority ma " +
18 | "left join sys_authority a on a.id = ma.authority_id ${ew.customSqlSegment}")
19 | IPage selectByMenu(@Param("page") Page page,
20 | @Param(Constants.WRAPPER) LambdaQueryWrapper wrapper);
21 | }
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/mapper/SysMenuAuthorityMapper.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.mapper;
2 |
3 | import cn.flizi.ext.rbac.entity.SysMenuAuthority;
4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 | import org.apache.ibatis.annotations.Mapper;
6 |
7 | @Mapper
8 | public interface SysMenuAuthorityMapper extends BaseMapper {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/mapper/SysMenuMapper.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.mapper;
2 |
3 | import cn.flizi.ext.rbac.entity.SysMenu;
4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 | import org.apache.ibatis.annotations.Mapper;
6 |
7 | @Mapper
8 | public interface SysMenuMapper extends BaseMapper {
9 | }
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/mapper/SysRoleMapper.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.mapper;
2 |
3 | import cn.flizi.ext.rbac.entity.SysRole;
4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 | import com.baomidou.mybatisplus.core.metadata.IPage;
6 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
7 | import org.apache.ibatis.annotations.Mapper;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * @author : zhiyi
13 | * Date: 2020/2/11
14 | */
15 | @Mapper
16 | public interface SysRoleMapper extends BaseMapper {
17 |
18 | IPage getPage(Page page);
19 |
20 | List selectAuthoritiesByRole(Integer roleId);
21 | }
22 |
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/mapper/SysRoleMenuMapper.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.mapper;
2 |
3 | import cn.flizi.ext.rbac.entity.SysRoleMenu;
4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 | import org.apache.ibatis.annotations.Mapper;
6 |
7 | @Mapper
8 | public interface SysRoleMenuMapper extends BaseMapper {
9 |
10 | }
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/mapper/SysRoleUserMapper.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.mapper;
2 |
3 | import cn.flizi.ext.rbac.entity.SysRoleUser;
4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 | import org.apache.ibatis.annotations.Mapper;
6 |
7 | /**
8 | * @author : zhiyi
9 | * Date: 2020/2/11
10 | */
11 | @Mapper
12 | public interface SysRoleUserMapper extends BaseMapper {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/service/SysAuthorityService.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.service;
2 |
3 | import cn.flizi.ext.rbac.entity.SysAuthority;
4 | import cn.flizi.ext.rbac.entity.SysMenuAuthority;
5 | import cn.flizi.ext.rbac.mapper.SysAuthorityMapper;
6 | import com.baomidou.mybatisplus.core.metadata.IPage;
7 | import com.baomidou.mybatisplus.core.toolkit.Wrappers;
8 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
9 | import com.baomidou.mybatisplus.extension.service.IService;
10 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
11 | import org.springframework.stereotype.Service;
12 |
13 | @Service
14 | public class SysAuthorityService extends ServiceImpl implements IService {
15 |
16 | public IPage pageByMenu(Integer menuId, Page page) {
17 | return baseMapper.selectByMenu(page,
18 | Wrappers.lambdaQuery()
19 | .eq(SysMenuAuthority::getMenuId, menuId)
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/service/SysMenuService.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.service;
2 |
3 | import cn.flizi.core.util.R;
4 | import cn.flizi.core.util.TreeUtils;
5 | import cn.flizi.ext.rbac.entity.SysMenu;
6 | import cn.flizi.ext.rbac.mapper.SysMenuMapper;
7 | import cn.hutool.core.lang.tree.Tree;
8 | import cn.hutool.core.lang.tree.TreeUtil;
9 | import cn.hutool.core.util.StrUtil;
10 | import com.baomidou.mybatisplus.core.toolkit.Wrappers;
11 | import com.baomidou.mybatisplus.extension.service.IService;
12 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
13 | import lombok.AllArgsConstructor;
14 | import org.springframework.stereotype.Service;
15 |
16 | import java.util.List;
17 |
18 | @Service
19 | @AllArgsConstructor
20 | public class SysMenuService extends ServiceImpl implements IService {
21 |
22 | public Object sort(Integer menuId, Integer index) {
23 | return null;
24 | }
25 |
26 | public Object removeForce(Integer id) {
27 | return null;
28 | }
29 |
30 | public Object addAuthorityByMenu(Integer menuId, List ids) {
31 | return null;
32 | }
33 |
34 | public Object removeAuthorityByMenu(Integer menuId, List ids) {
35 | return null;
36 | }
37 |
38 | public R> getTree() {
39 | List menuList = baseMapper.selectList(Wrappers.emptyWrapper());
40 | List> authorityTree = TreeUtil.build(menuList, -1, (treeNode, tree) -> {
41 | tree.setId(treeNode.getId());
42 | tree.setParentId(treeNode.getParentId());
43 | tree.setWeight(treeNode.getWeight());
44 | tree.setName(treeNode.getName());
45 | tree.putExtra("path", treeNode.getPath());
46 | tree.putExtra("type", treeNode.getType());
47 | tree.putExtra("component", treeNode.getComponent());
48 | tree.putExtra("hidden", treeNode.getHidden());
49 | tree.putExtra("alwaysShow", treeNode.getAlwaysShow());
50 | tree.putExtra("redirect", treeNode.getRedirect());
51 | tree.putExtra("title", treeNode.getTitle());
52 | tree.putExtra("icon", treeNode.getIcon());
53 | tree.putExtra("authority", treeNode.getAuthority());
54 | tree.putExtra("breadcrumb", treeNode.getBreadcrumb());
55 | });
56 | if (authorityTree.size() == 0) {
57 | return R.ok(menuList);
58 | }
59 | // 排序
60 | TreeUtils.computeSort(authorityTree);
61 | // 计算路径
62 | computePath(authorityTree);
63 | return R.ok(authorityTree);
64 | }
65 |
66 | public static void computePath(List extends Tree> trees) {
67 | if (trees == null || trees.size() == 0) {
68 | return;
69 | }
70 | for (Tree parent : trees) {
71 | if (parent.getChildren() == null) {
72 | return;
73 | }
74 | parent.putExtra("absPath", parent.get("path"));
75 | for (Tree child : parent.getChildren()) {
76 | String path1 = (String) parent.get("path");
77 | String path2 = (String) child.get("path");
78 | if (!StrUtil.startWithAny(path2, "http", "https")) {
79 | child.putExtra("absPath", path1 + "/" + path2);
80 | } else {
81 | child.putExtra("absPath", path2);
82 | }
83 | }
84 | computePath(parent.getChildren());
85 | }
86 | }
87 |
88 | }
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/service/SysRoleMenuService.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.service;
2 |
3 | import cn.flizi.ext.rbac.entity.SysRoleMenu;
4 | import cn.flizi.ext.rbac.mapper.SysRoleMenuMapper;
5 | import com.baomidou.mybatisplus.core.toolkit.Wrappers;
6 | import com.baomidou.mybatisplus.extension.service.IService;
7 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
8 | import lombok.AllArgsConstructor;
9 | import org.springframework.stereotype.Service;
10 |
11 | import java.util.Arrays;
12 | import java.util.List;
13 | import java.util.stream.Collectors;
14 |
15 | @Service
16 | @AllArgsConstructor
17 | public class SysRoleMenuService extends ServiceImpl implements IService {
18 |
19 | /**
20 | * 全更新, 会先删除说有绑定关系,再重新添加
21 | *
22 | * @param roleId 角色id
23 | * @param menus 菜单ids
24 | */
25 | public Boolean updateRoleMenu(Integer roleId, Integer[] menus) {
26 | List roleMenuList = Arrays.stream(menus).map(menuId -> {
27 | SysRoleMenu roleMenu = new SysRoleMenu();
28 | roleMenu.setRoleId(roleId);
29 | roleMenu.setMenuId(menuId);
30 | return roleMenu;
31 | }).collect(Collectors.toList());
32 |
33 | remove(Wrappers.lambdaQuery()
34 | .eq(SysRoleMenu::getRoleId, roleId));
35 | saveBatch(roleMenuList);
36 | return true;
37 | }
38 | }
--------------------------------------------------------------------------------
/extension/src/main/java/cn/flizi/ext/rbac/service/SysRoleService.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac.service;
2 |
3 | import cn.flizi.ext.rbac.dto.SysRoleAdd;
4 | import cn.flizi.ext.rbac.dto.SysRoleUpdate;
5 | import cn.flizi.ext.rbac.entity.SysRole;
6 | import cn.flizi.ext.rbac.mapper.SysRoleMapper;
7 | import com.baomidou.mybatisplus.core.toolkit.Wrappers;
8 | import com.baomidou.mybatisplus.extension.service.IService;
9 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
10 | import lombok.AllArgsConstructor;
11 | import org.springframework.beans.BeanUtils;
12 | import org.springframework.stereotype.Service;
13 | import org.springframework.util.Assert;
14 |
15 | @Service
16 | @AllArgsConstructor
17 | public class SysRoleService extends ServiceImpl implements IService {
18 |
19 | private final SysRoleMenuService sysRoleMenuService;
20 |
21 | /**
22 | * 创建角色
23 | *
24 | * @param body
25 | * @return
26 | */
27 | public Boolean saveRole(SysRoleAdd body) {
28 | // 检查
29 | int count = count(Wrappers.lambdaQuery().eq(SysRole::getName, body.getName()));
30 | Assert.isTrue(count > 0, "角色标识重复");
31 |
32 | // 创建
33 | SysRole sysRole = new SysRole();
34 | BeanUtils.copyProperties(body, sysRole);
35 | sysRole.insert();
36 |
37 | // 更新关联菜单
38 | return sysRoleMenuService.updateRoleMenu(sysRole.getId(), body.getMenus());
39 | }
40 |
41 |
42 | /**
43 | * 更新角色
44 | *
45 | * @param body
46 | * @return
47 | */
48 | public Boolean updateRole(SysRoleUpdate body) {
49 | // 检查
50 | Integer roleId = body.getId();
51 | SysRole role = getById(roleId);
52 | Assert.notNull(role, "确实不存在");
53 | Assert.isTrue(!role.getIsLock(), "当前角色状态被锁定,禁止修改");
54 |
55 | // 更新关联菜单
56 | return sysRoleMenuService.updateRoleMenu(roleId, body.getMenus());
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/extension/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2 | cn.flizi.ext.rbac.RbacConfiguration
3 |
--------------------------------------------------------------------------------
/extension/src/main/resources/mapper/SysMenuMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/extension/src/main/resources/mapper/SysRoleMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/extension/ui/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/extension/ui/.env.development:
--------------------------------------------------------------------------------
1 | # just a flag
2 | ENV = 'development'
3 |
4 | # base api
5 | VUE_APP_BASE_API = 'http://localhost:8080/'
6 | VUE_APP_OAUTH2_API = 'http://localhost:8080/'
7 |
--------------------------------------------------------------------------------
/extension/ui/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | src/assets
3 | public
4 | dist
5 |
--------------------------------------------------------------------------------
/extension/ui/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 | logs/
35 |
36 |
37 | *.java.hsp
38 | *.sonarj
39 | *.sw*
40 | .DS_Store
41 | bin
42 | build.sh
43 | integration-repo
44 | ivy-cache
45 | jxl.log
46 | jmx.log
47 | derby.log
48 | spring-test/test-output/
49 | .gradle
50 | argfile*
51 | activemq-data/
52 |
53 | classes/
54 | /build
55 | buildSrc/build
56 | /spring-*/build
57 | /spring-core/kotlin-coroutines/build
58 | /framework-bom/build
59 | /integration-tests/build
60 | /src/asciidoc/build
61 | /build/
62 | application-dev.yml
63 |
64 | # Eclipse artifacts, including WTP generated manifests
65 | spring-*/src/main/java/META-INF/MANIFEST.MF
66 |
67 | # IDEA artifacts and output dirs
68 | out
69 | test-output
70 | atlassian-ide-plugin.xml
71 | .gradletasknamecache
72 |
73 |
74 | node_modules/
75 | dist/
76 | npm-debug.log*
77 | yarn-debug.log*
78 | yarn-error.log*
79 | package-lock.json
80 | tests/**/coverage/
81 |
82 | # Editor directories and files
83 | .vscode
84 | *.suo
85 | *.ntvs*
86 | *.njsproj
87 | *.sln
88 |
89 | node_modules
90 | *.log
91 | .temp
92 |
93 | http-client.**
94 | generate/
95 |
--------------------------------------------------------------------------------
/extension/ui/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: 10
3 | script: npm run test
4 | notifications:
5 | email: false
6 |
--------------------------------------------------------------------------------
/extension/ui/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
4 | '@vue/cli-plugin-babel/preset'
5 | ],
6 | 'env': {
7 | 'development': {
8 | // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
9 | // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
10 | // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
11 | 'plugins': ['dynamic-import-node']
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/extension/ui/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
3 | transform: {
4 | '^.+\\.vue$': 'vue-jest',
5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
6 | 'jest-transform-stub',
7 | '^.+\\.jsx?$': 'babel-jest'
8 | },
9 | moduleNameMapper: {
10 | '^@/(.*)$': '/src/$1'
11 | },
12 | snapshotSerializers: ['jest-serializer-vue'],
13 | testMatch: [
14 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
15 | ],
16 | collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
17 | coverageDirectory: '/tests/unit/coverage',
18 | // 'collectCoverage': true,
19 | 'coverageReporters': [
20 | 'lcov',
21 | 'text-summary'
22 | ],
23 | testURL: 'http://localhost/'
24 | }
25 |
--------------------------------------------------------------------------------
/extension/ui/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "@/*": ["src/*"]
6 | }
7 | },
8 | "exclude": ["node_modules", "dist"]
9 | }
10 |
--------------------------------------------------------------------------------
/extension/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tao-ui",
3 | "version": "0.0.1",
4 | "description": "github.com/taoroot/oauth2-server",
5 | "author": "taoroot ",
6 | "scripts": {
7 | "dev": "vue-cli-service serve",
8 | "build:prod": "vue-cli-service build",
9 | "build:stage": "vue-cli-service build --mode staging",
10 | "preview": "node build/index.js --preview",
11 | "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
12 | "lint": "eslint --ext .js,.vue src",
13 | "test:unit": "jest --clearCache && vue-cli-service test:unit",
14 | "test:ci": "npm run lint && npm run test:unit"
15 | },
16 | "dependencies": {
17 | "vue-count-to": "1.0.13",
18 | "echarts": "^4.8.0",
19 | "less": "^3.0.4",
20 | "less-loader": "^4.1.0",
21 | "@riophae/vue-treeselect": "0.4.0",
22 | "axios": "0.18.1",
23 | "core-js": "3.6.5",
24 | "element-ui": "2.13.2",
25 | "js-cookie": "2.2.0",
26 | "normalize.css": "7.0.0",
27 | "nprogress": "0.2.0",
28 | "path-to-regexp": "2.4.0",
29 | "vue": "2.6.10",
30 | "vue-cropper": "^0.5.5",
31 | "vue-router": "3.0.6",
32 | "vuex": "3.1.0"
33 | },
34 | "devDependencies": {
35 | "@vue/cli-plugin-babel": "4.4.4",
36 | "@vue/cli-plugin-eslint": "4.4.4",
37 | "@vue/cli-plugin-unit-jest": "4.4.4",
38 | "@vue/cli-service": "4.4.4",
39 | "@vue/test-utils": "1.0.0-beta.29",
40 | "autoprefixer": "9.5.1",
41 | "babel-eslint": "10.1.0",
42 | "babel-jest": "23.6.0",
43 | "babel-plugin-dynamic-import-node": "2.3.3",
44 | "chalk": "2.4.2",
45 | "connect": "3.6.6",
46 | "eslint": "6.7.2",
47 | "eslint-plugin-vue": "6.2.2",
48 | "html-webpack-plugin": "3.2.0",
49 | "mockjs": "1.0.1-beta3",
50 | "runjs": "4.3.2",
51 | "sass": "1.26.8",
52 | "sass-loader": "8.0.2",
53 | "script-ext-html-webpack-plugin": "2.1.3",
54 | "serve-static": "1.13.2",
55 | "svg-sprite-loader": "4.1.3",
56 | "svgo": "1.2.2",
57 | "vue-template-compiler": "2.6.10"
58 | },
59 | "browserslist": [
60 | "> 1%",
61 | "last 2 versions"
62 | ],
63 | "engines": {
64 | "node": ">=8.9",
65 | "npm": ">= 3.0.0"
66 | },
67 | "license": "MIT"
68 | }
69 |
--------------------------------------------------------------------------------
/extension/ui/postcss.config.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | 'plugins': {
5 | // to edit target browsers: use "browserslist" field in package.json
6 | 'autoprefixer': {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/extension/ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= webpackConfig.name %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/extension/ui/public/oauth2-callback.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | OAUTH2 回 调
8 |
9 |
10 |
11 |
OAUTH2 回 调
12 |
13 |
14 |
45 |
--------------------------------------------------------------------------------
/extension/ui/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/extension/ui/src/api/login.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | var Authorization = 'BASIC ' + btoa('test:secret')
4 | var VUE_APP_OAUTH2_API = process.env.VUE_APP_OAUTH2_API
5 |
6 | /**
7 | * OAuth2 密码登录
8 | * @param {*} data
9 | * @param {*} params
10 | * @returns
11 | */
12 | export function oauth2Login(data, params) {
13 | return request({
14 | url: VUE_APP_OAUTH2_API + 'oauth/token',
15 | method: 'post',
16 | headers: {
17 | Authorization: Authorization
18 | },
19 | data: data,
20 | params: params
21 | })
22 | }
23 |
24 | export function oauth2CodeLogin(params) {
25 | return request({
26 | url: '/token',
27 | method: 'post',
28 | params: params
29 | })
30 | }
31 |
32 | export function getInfo() {
33 | return request({
34 | url: '/user_info',
35 | method: 'get'
36 | })
37 | }
38 |
39 | export function loginPhone(params) {
40 | return request({
41 | url: '/sms',
42 | method: 'post',
43 | params
44 | })
45 | }
46 |
47 | export function getUserSocial() {
48 | return request({
49 | url: '/upms/user_socials',
50 | method: 'get'
51 | })
52 | }
53 |
54 | export function getUserProfile() {
55 | return request({
56 | url: '/upms/user_info',
57 | method: 'get'
58 | })
59 | }
60 |
61 | export function unbindUserSocial(id) {
62 | return request({
63 | url: `/upms/user_social/${id}`,
64 | method: 'delete'
65 | })
66 | }
67 |
68 | export function updateUser(data) {
69 | return request({
70 | url: `/upms/user_info`,
71 | method: 'put',
72 | data
73 | })
74 | }
75 |
76 | export function resetPassword(data) {
77 | return request({
78 | url: `/upms/user_password`,
79 | method: 'put',
80 | data
81 | })
82 | }
83 |
84 | export function getSms({ phone }) {
85 | return request({
86 | url: `https://auth.flizi.cn/sms?phone=${phone}`,
87 | method: 'post'
88 | })
89 | }
90 |
--------------------------------------------------------------------------------
/extension/ui/src/api/sys/authority.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | /**
4 | * 菜单具有的权限
5 | * @param {*} params
6 | */
7 | export const getMenuAuthoritys = (params) => {
8 | return request({
9 | url: '/api/sys/menu/authority/page',
10 | method: 'get',
11 | params
12 | })
13 | }
14 |
15 | /**
16 | * 菜单具有的权限新增
17 | * @param {*} menuId
18 | * @param {*} data
19 | */
20 | export const updateMenuAuthority = (menuId, data) => {
21 | return request({
22 | url: `/sys/menu/authority?menuId=${menuId}`,
23 | method: 'post',
24 | data
25 | })
26 | }
27 |
28 | /**
29 | * 菜单具有的权限移除
30 | * @param {*} menuId
31 | * @param {*} data
32 | */
33 | export const deleteMenuAuthority = (menuId, data) => {
34 | return request({
35 | url: `/sys/menu/authority?menuId=${menuId}`,
36 | method: 'delete',
37 | data
38 | })
39 | }
40 |
41 | /**
42 | * 所有菜单
43 | * @param {*} params
44 | */
45 | export const getAuthoritys = (params) => {
46 | return request({
47 | url: '/sys/authority/page',
48 | method: 'get',
49 | params
50 | })
51 | }
52 |
--------------------------------------------------------------------------------
/extension/ui/src/api/sys/menu.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | /**
4 | * 菜单树
5 | * @param {分页参数} params
6 | */
7 | export const menuTree = (params) => {
8 | return request({
9 | url: '/api/sys/menu/tree',
10 | method: 'get',
11 | params
12 | })
13 | }
14 |
15 | /**
16 | * 新增
17 | * @param {数据体} data
18 | */
19 | export const menuAdd = (data) => {
20 | return request({
21 | url: '/api/sys/menu',
22 | method: 'post',
23 | data: data
24 | })
25 | }
26 |
27 | /**
28 | * 批量删除
29 | * @param {id数组} ids
30 | */
31 | export const menuDel = (ids) => {
32 | return request({
33 | url: '/api/sys/menu',
34 | method: 'delete',
35 | params: {
36 | ids
37 | }
38 | })
39 | }
40 |
41 | /**
42 | * 强制删除
43 | * @param {主键} id
44 | */
45 | export const menuDelForce = (id) => {
46 | return request({
47 | url: '/api/sys/menu/force',
48 | method: 'delete',
49 | params: {
50 | id
51 | }
52 | })
53 | }
54 |
55 | /**
56 | * 更新
57 | * @param {数据体} data
58 | */
59 | export function menuUpdate(data) {
60 | return request({
61 | url: '/api/sys/menu',
62 | method: 'put',
63 | data: data
64 | })
65 | }
66 |
67 | /**
68 | * 排序
69 | * @param {排序参数} params
70 | */
71 | export function menuSort(params) {
72 | return request({
73 | url: '/api/sys/menu/sort',
74 | method: 'put',
75 | params
76 | })
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/extension/ui/src/api/sys/role.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | /**
4 | * 分页
5 | * @param {分页参数} params
6 | */
7 | export function rolePage(params) {
8 | return request({
9 | url: '/api/sys/role/page',
10 | method: 'get',
11 | params
12 | })
13 | }
14 |
15 | /**
16 | * 批量删除
17 | * @param {id数组} ids
18 | */
19 | export function roleDel(ids) {
20 | return request({
21 | url: `/api/sys/role`,
22 | method: 'delete',
23 | params: {
24 | ids: ids
25 | }
26 | })
27 | }
28 |
29 | /**
30 | * 新增
31 | * @param {数据体} data
32 | */
33 | export function roleAdd(data) {
34 | return request({
35 | url: '/api/sys/role',
36 | method: 'post',
37 | data
38 | })
39 | }
40 |
41 | /**
42 | * 更新
43 | * @param {数据体} data
44 | */
45 | export function roleUpdate(data) {
46 | return request({
47 | url: `/api/sys/role`,
48 | method: 'put',
49 | data
50 | })
51 | }
52 |
--------------------------------------------------------------------------------
/extension/ui/src/api/sys/user.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | /**
4 | * 分页
5 | * @param {分页参数} params
6 | */
7 | export function userPage(params) {
8 | return request({
9 | url: '/sys/user/page',
10 | method: 'get',
11 | params
12 | })
13 | }
14 |
15 | /**
16 | * 批量删除
17 | * @param {id数组} ids
18 | */
19 | export function userDel(ids) {
20 | return request({
21 | url: `/sys/user`,
22 | method: 'delete',
23 | params: {
24 | ids: ids
25 | }
26 | })
27 | }
28 |
29 | /**
30 | * 新增
31 | * @param {数据体} data
32 | */
33 | export function userAdd(data) {
34 | return request({
35 | url: '/sys/user',
36 | method: 'post',
37 | data
38 | })
39 | }
40 |
41 | /**
42 | * 更新
43 | * @param {数据体} data
44 | */
45 | export function userUpdate(data) {
46 | return request({
47 | url: `/sys/user`,
48 | method: 'put',
49 | data
50 | })
51 | }
52 |
53 | /**
54 | * 更新用户状态
55 | * @param {用户ID} id
56 | * @param {状态} enabled
57 | */
58 | export function userStatusChange(id, enabled) {
59 | const data = {
60 | id,
61 | enabled
62 | }
63 | return request({
64 | url: `/sys/user`,
65 | method: 'put',
66 | data: data
67 | })
68 | }
69 |
--------------------------------------------------------------------------------
/extension/ui/src/assets/404_images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/404_images/404.png
--------------------------------------------------------------------------------
/extension/ui/src/assets/404_images/404_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/404_images/404_cloud.png
--------------------------------------------------------------------------------
/extension/ui/src/assets/pic/green_addr@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/pic/green_addr@2x.png
--------------------------------------------------------------------------------
/extension/ui/src/assets/pic/null_addr@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/pic/null_addr@2x.png
--------------------------------------------------------------------------------
/extension/ui/src/assets/pic/orange_addr@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/pic/orange_addr@2x.png
--------------------------------------------------------------------------------
/extension/ui/src/assets/pic/red_addr@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/pic/red_addr@2x.png
--------------------------------------------------------------------------------
/extension/ui/src/assets/pic/yellow_addr@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taoroot/oauth2-server/757fb7beb8112ea5f2cfa16cd2005cda899c9236/extension/ui/src/assets/pic/yellow_addr@2x.png
--------------------------------------------------------------------------------
/extension/ui/src/components/Breadcrumb/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ item.meta.title }}
6 | {{ item.meta.title }}
7 |
8 |
9 |
10 |
11 |
12 |
65 |
66 |
79 |
--------------------------------------------------------------------------------
/extension/ui/src/components/Hamburger/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
32 |
33 |
45 |
--------------------------------------------------------------------------------
/extension/ui/src/components/IconSelect/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ item }}
11 |
12 |
13 |
14 |
15 |
16 |
45 |
46 |
70 |
--------------------------------------------------------------------------------
/extension/ui/src/components/IconSelect/requireIcons.js:
--------------------------------------------------------------------------------
1 |
2 | const req = require.context('../../icons/svg', false, /\.svg$/)
3 | const requireAll = requireContext => requireContext.keys()
4 |
5 | const re = /\.\/(.*)\.svg/
6 |
7 | const icons = requireAll(req).map(i => {
8 | return i.match(re)[1]
9 | })
10 |
11 | export default icons
12 |
--------------------------------------------------------------------------------
/extension/ui/src/components/RightPanel/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
77 |
78 |
85 |
86 |
140 |
--------------------------------------------------------------------------------
/extension/ui/src/components/SvgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
47 |
48 |
63 |
--------------------------------------------------------------------------------
/extension/ui/src/directive/permission/index.js:
--------------------------------------------------------------------------------
1 | import permission from './permission'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('permission', permission)
5 | }
6 |
7 | if (window.Vue) {
8 | window['permission'] = permission
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | permission.install = install
13 | export default permission
14 |
--------------------------------------------------------------------------------
/extension/ui/src/directive/permission/permission.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | function checkPermission(el, binding) {
4 | const { value } = binding
5 | const roles = store.getters && store.getters.roles
6 |
7 | if (value && value instanceof Array) {
8 | if (value.length > 0) {
9 | const permissionRoles = value
10 |
11 | const hasPermission = roles.some(role => {
12 | return permissionRoles.includes(role)
13 | })
14 |
15 | if (!hasPermission) {
16 | el.parentNode && el.parentNode.removeChild(el)
17 | }
18 | }
19 | } else {
20 | throw new Error(`need roles! Like v-permission="['admin','editor']"`)
21 | }
22 | }
23 |
24 | export default {
25 | inserted(el, binding) {
26 | checkPermission(el, binding)
27 | },
28 | update(el, binding) {
29 | checkPermission(el, binding)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/extension/ui/src/icons/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import SvgIcon from '@/components/SvgIcon'// svg component
3 |
4 | // register globally
5 | Vue.component('svg-icon', SvgIcon)
6 |
7 | const req = require.context('./svg', false, /\.svg$/)
8 | const requireAll = requireContext => requireContext.keys().map(requireContext)
9 | requireAll(req)
10 |
--------------------------------------------------------------------------------
/extension/ui/src/icons/svg/light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/extension/ui/src/icons/svg/money.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/extension/ui/src/icons/svg/password.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/extension/ui/src/icons/svg/sun.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/extension/ui/src/icons/svg/user.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/extension/ui/src/icons/svgo.yml:
--------------------------------------------------------------------------------
1 | # replace default config
2 |
3 | # multipass: true
4 | # full: true
5 |
6 | plugins:
7 |
8 | # - name
9 | #
10 | # or:
11 | # - name: false
12 | # - name: true
13 | #
14 | # or:
15 | # - name:
16 | # param1: 1
17 | # param2: 2
18 |
19 | - removeAttrs:
20 | attrs:
21 | - 'fill'
22 | - 'fill-rule'
23 |
--------------------------------------------------------------------------------
/extension/ui/src/layout/components/AppMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
19 |
20 |
32 |
33 |
41 |
--------------------------------------------------------------------------------
/extension/ui/src/layout/components/Settings/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
页面设置
5 |
6 |
7 | 主题颜色
8 |
9 |
10 |
11 |
12 | 开启任务栏
13 |
14 |
15 |
16 |
17 | Header 固定
18 |
19 |
20 |
21 |
22 | 侧边栏Logo
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
82 |
83 |
105 |
--------------------------------------------------------------------------------
/extension/ui/src/layout/components/Sidebar/FixiOSBug.js:
--------------------------------------------------------------------------------
1 | export default {
2 | computed: {
3 | device() {
4 | return this.$store.state.app.device
5 | }
6 | },
7 | mounted() {
8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug
9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135
10 | this.fixBugIniOS()
11 | },
12 | methods: {
13 | fixBugIniOS() {
14 | const $subMenu = this.$refs.subMenu
15 | if ($subMenu) {
16 | const handleMouseleave = $subMenu.handleMouseleave
17 | $subMenu.handleMouseleave = (e) => {
18 | if (this.device === 'mobile') {
19 | return
20 | }
21 | handleMouseleave(e)
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/extension/ui/src/layout/components/Sidebar/Item.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
42 |
--------------------------------------------------------------------------------
/extension/ui/src/layout/components/Sidebar/Link.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
44 |
--------------------------------------------------------------------------------
/extension/ui/src/layout/components/Sidebar/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
33 |
34 |
83 |
--------------------------------------------------------------------------------
/extension/ui/src/layout/components/Sidebar/SidebarItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
24 |
25 |
26 |
27 |
96 |
--------------------------------------------------------------------------------
/extension/ui/src/layout/components/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
58 |
--------------------------------------------------------------------------------
/extension/ui/src/layout/components/TagsView/ScrollPane.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
71 |
72 |
88 |
--------------------------------------------------------------------------------
/extension/ui/src/layout/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Navbar } from './Navbar'
2 | export { default as Sidebar } from './Sidebar'
3 | export { default as Settings } from './Settings'
4 | export { default as AppMain } from './AppMain'
5 | export { default as TagsView } from './TagsView/index.vue'
6 |
--------------------------------------------------------------------------------
/extension/ui/src/layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
58 |
59 |
96 |
--------------------------------------------------------------------------------
/extension/ui/src/layout/mixin/ResizeHandler.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | const { body } = document
4 | const WIDTH = 992 // refer to Bootstrap's responsive design
5 |
6 | export default {
7 | watch: {
8 | $route(route) {
9 | if (this.device === 'mobile' && this.sidebar.opened) {
10 | store.dispatch('app/closeSideBar', { withoutAnimation: false })
11 | }
12 | }
13 | },
14 | beforeMount() {
15 | window.addEventListener('resize', this.$_resizeHandler)
16 | },
17 | beforeDestroy() {
18 | window.removeEventListener('resize', this.$_resizeHandler)
19 | },
20 | mounted() {
21 | const isMobile = this.$_isMobile()
22 | if (isMobile) {
23 | store.dispatch('app/toggleDevice', 'mobile')
24 | store.dispatch('app/closeSideBar', { withoutAnimation: true })
25 | }
26 | },
27 | methods: {
28 | // use $_ for mixins properties
29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
30 | $_isMobile() {
31 | const rect = body.getBoundingClientRect()
32 | return rect.width - 1 < WIDTH
33 | },
34 | $_resizeHandler() {
35 | if (!document.hidden) {
36 | const isMobile = this.$_isMobile()
37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
38 |
39 | if (isMobile) {
40 | store.dispatch('app/closeSideBar', { withoutAnimation: true })
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/extension/ui/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets
4 |
5 | import ElementUI from 'element-ui'
6 | import 'element-ui/lib/theme-chalk/index.css'
7 |
8 | import '@/styles/index.scss' // global css
9 |
10 | import App from './App'
11 | import store from './store'
12 | import router from './router'
13 |
14 | import '@/icons' // icon
15 | import '@/permission' // permission control
16 |
17 | // 权限判断指令
18 | import permission from './directive/permission/index.js'
19 | Vue.use(permission)
20 |
21 | // set ElementUI lang to EN
22 | // Vue.use(ElementUI, { locale })
23 | // 如果想要中文版 element-ui,按如下方式声明
24 | Vue.use(ElementUI)
25 |
26 | Vue.config.productionTip = false
27 |
28 | new Vue({
29 | el: '#app',
30 | router,
31 | store,
32 | render: h => h(App)
33 | })
34 |
--------------------------------------------------------------------------------
/extension/ui/src/permission.js:
--------------------------------------------------------------------------------
1 | import router from './router'
2 | import store from './store'
3 | import { Message } from 'element-ui'
4 | import NProgress from 'nprogress' // progress bar
5 | import 'nprogress/nprogress.css' // progress bar style
6 | import { getToken } from '@/utils/auth' // get token from cookie
7 | import getPageTitle from '@/utils/get-page-title'
8 |
9 | NProgress.configure({ showSpinner: false }) // NProgress Configuration
10 |
11 | const whiteList = ['/login', '/login/index', '/auth-redirect'] // no redirect whitelist
12 |
13 | router.beforeEach(async(to, from, next) => {
14 | // start progress bar
15 | NProgress.start()
16 |
17 | // set page title
18 | document.title = getPageTitle(to.meta.title)
19 |
20 | // determine whether the user has logged in
21 | const hasToken = getToken()
22 |
23 | if (hasToken) {
24 | if (to.path === '/login') {
25 | // if is logged in, redirect to the home page
26 | next({ path: '/' })
27 | NProgress.done()
28 | } else {
29 | const hasGetUserInfo = store.getters.name
30 | if (hasGetUserInfo) {
31 | next()
32 | } else {
33 | try {
34 | // get user info
35 | const { menus } = await store.dispatch('user/getInfo')
36 |
37 | const accessRoutes = await store.dispatch('permission/generateRoutes', menus)
38 |
39 | router.addRoutes(accessRoutes)
40 |
41 | next({ ...to, replace: true })
42 | } catch (error) {
43 | // remove token and go to login page to re-login
44 | await store.dispatch('user/resetToken')
45 | Message.error(error || 'Has Error')
46 | next(`/login?redirect=${to.path}`)
47 | NProgress.done()
48 | }
49 | }
50 | }
51 | } else {
52 | /* has no token*/
53 |
54 | if (whiteList.indexOf(to.path) !== -1) {
55 | // in the free login whitelist, go directly
56 | next()
57 | } else {
58 | // other pages that do not have permission to access are redirected to the login page.
59 | next(`/login?redirect=${to.path}`)
60 | NProgress.done()
61 | }
62 | }
63 | })
64 |
65 | router.afterEach(() => {
66 | // finish progress bar
67 | NProgress.done()
68 | })
69 |
--------------------------------------------------------------------------------
/extension/ui/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | Vue.use(Router)
5 |
6 | /* Layout */
7 | import Layout from '@/layout'
8 |
9 | /**
10 | * Note: sub-menu only appear when route children.length >= 1
11 | * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
12 | *
13 | * hidden: true if set true, item will not show in the sidebar(default is false)
14 | * alwaysShow: true if set true, will always show the root menu
15 | * if not set alwaysShow, when item has more than one children route,
16 | * it will becomes nested mode, otherwise not show the root menu
17 | * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
18 | * name:'router-name' the name is used by (must set!!!)
19 | * meta : {
20 | roles: ['admin','editor'] control the page roles (you can set multiple roles)
21 | title: 'title' the name show in sidebar and breadcrumb (recommend set)
22 | icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
23 | breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
24 | activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
25 | }
26 | */
27 |
28 | /**
29 | * constantRoutes
30 | * a base page that does not have permission requirements
31 | * all roles can be accessed
32 | */
33 | export const constantRoutes = [
34 | {
35 | path: '/login',
36 | component: () => import('@/views/login/index'),
37 | hidden: true
38 | },
39 |
40 | {
41 | path: '/auth-redirect',
42 | component: () => import('@/views/login/auth-redirect'),
43 | hidden: true
44 | },
45 |
46 | {
47 | path: '/404',
48 | component: () => import('@/views/404'),
49 | hidden: true
50 | },
51 |
52 | // {
53 | // path: '/login',
54 | // component: Layout,
55 | // redirect: '/login/index',
56 | // children: [{
57 | // path: '/index',
58 | // component: () => import('@/views/login/index'),
59 | // hidden: true
60 | // }]
61 | // },
62 | {
63 | path: '/',
64 | component: Layout,
65 | children: [{
66 | path: 'Dashboard',
67 | name: 'Dashboard',
68 | component: () => import('@/views/dashboard/index'),
69 | meta: { title: 'Dashboard', icon: 'dashboard' }
70 | }]
71 | },
72 |
73 | {
74 | path: '/menu',
75 | component: Layout,
76 | redirect: '/',
77 | children: [{
78 | path: 'menu',
79 | name: 'menu',
80 | component: () => import('@/views/menu/index'),
81 | meta: { title: '权限管理', icon: 'menu' }
82 | }]
83 | }
84 |
85 | // 404 page must be placed at the end !!!
86 | // { path: '*', redirect: '/404', hidden: false }
87 | ]
88 |
89 | const createRouter = () => new Router({
90 | // mode: 'history', // require service support
91 | scrollBehavior: () => ({ y: 0 }),
92 | routes: constantRoutes
93 | })
94 |
95 | const router = createRouter()
96 |
97 | // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
98 | export function resetRouter() {
99 | const newRouter = createRouter()
100 | router.matcher = newRouter.matcher // reset router
101 | }
102 |
103 | export default router
104 |
--------------------------------------------------------------------------------
/extension/ui/src/settings.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: '前端分离完整演示',
3 |
4 | /**
5 | * @type {boolean} true | false
6 | * @description Whether show the settings right-panel
7 | */
8 | showSettings: false,
9 |
10 | /**
11 | * @type {boolean} true | false
12 | * @description Whether need tagsView
13 | */
14 | tagsView: true,
15 |
16 | /**
17 | * @type {boolean} true | false
18 | * @description Whether fix the header
19 | */
20 | fixedHeader: true,
21 |
22 | /**
23 | * @type {boolean} true | false
24 | * @description Whether show the logo in sidebar
25 | */
26 | sidebarLogo: true,
27 |
28 | /**
29 | * @type {string | array} 'production' | ['production', 'development']
30 | * @description Need show err logs component.
31 | * The default is only used in the production env
32 | * If you want to also use it in dev, you can pass ['production', 'development']
33 | */
34 | errorLog: 'production'
35 | }
36 |
--------------------------------------------------------------------------------
/extension/ui/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | sidebar: state => state.app.sidebar,
3 | device: state => state.app.device,
4 | token: state => state.user.token,
5 | avatar: state => state.user.avatar,
6 | visitedViews: state => state.tagsView.visitedViews,
7 | cachedViews: state => state.tagsView.cachedViews,
8 | permission_routes: state => state.permission.routes,
9 | roles: state => state.user.roles,
10 | name: state => state.user.name
11 | }
12 | export default getters
13 |
--------------------------------------------------------------------------------
/extension/ui/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import getters from './getters'
4 |
5 | Vue.use(Vuex)
6 |
7 | // https://webpack.js.org/guides/dependency-management/#requirecontext
8 | const modulesFiles = require.context('./modules', true, /\.js$/)
9 |
10 | // you do not need `import app from './modules/app'`
11 | // it will auto require all vuex module from modules file
12 | const modules = modulesFiles.keys().reduce((modules, modulePath) => {
13 | // set './app.js' => 'app'
14 | const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
15 | const value = modulesFiles(modulePath)
16 | modules[moduleName] = value.default
17 | return modules
18 | }, {})
19 |
20 | const store = new Vuex.Store({
21 | modules,
22 | getters
23 | })
24 |
25 | export default store
26 |
--------------------------------------------------------------------------------
/extension/ui/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const state = {
4 | sidebar: {
5 | opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
6 | withoutAnimation: false
7 | },
8 | device: 'desktop'
9 | }
10 |
11 | const mutations = {
12 | TOGGLE_SIDEBAR: state => {
13 | state.sidebar.opened = !state.sidebar.opened
14 | state.sidebar.withoutAnimation = false
15 | if (state.sidebar.opened) {
16 | Cookies.set('sidebarStatus', 1)
17 | } else {
18 | Cookies.set('sidebarStatus', 0)
19 | }
20 | },
21 | CLOSE_SIDEBAR: (state, withoutAnimation) => {
22 | Cookies.set('sidebarStatus', 0)
23 | state.sidebar.opened = false
24 | state.sidebar.withoutAnimation = withoutAnimation
25 | },
26 | TOGGLE_DEVICE: (state, device) => {
27 | state.device = device
28 | }
29 | }
30 |
31 | const actions = {
32 | toggleSideBar({ commit }) {
33 | commit('TOGGLE_SIDEBAR')
34 | },
35 | closeSideBar({ commit }, { withoutAnimation }) {
36 | commit('CLOSE_SIDEBAR', withoutAnimation)
37 | },
38 | toggleDevice({ commit }, device) {
39 | commit('TOGGLE_DEVICE', device)
40 | }
41 | }
42 |
43 | export default {
44 | namespaced: true,
45 | state,
46 | mutations,
47 | actions
48 | }
49 |
--------------------------------------------------------------------------------
/extension/ui/src/store/modules/permission.js:
--------------------------------------------------------------------------------
1 | import { constantRoutes } from '@/router'
2 | import Layout from '@/layout/index'
3 |
4 | const state = {
5 | routes: [],
6 | addRoutes: []
7 | }
8 |
9 | const mutations = {
10 | SET_ROUTES: (state, routes) => {
11 | state.addRoutes = routes
12 | state.routes = constantRoutes.concat(routes)
13 | }
14 | }
15 |
16 | const actions = {
17 | generateRoutes({ commit }, menus) {
18 | return new Promise(resolve => {
19 | var accessedRoutes = filterAsyncRouter(menus || [])
20 | accessedRoutes.push({ path: '*', redirect: '/404', hidden: false })
21 | commit('SET_ROUTES', accessedRoutes)
22 | resolve(accessedRoutes)
23 | })
24 | }
25 | }
26 |
27 | // 遍历后台传来的路由字符串,转换为组件对象
28 | function filterAsyncRouter(asyncRouterMap) {
29 | return asyncRouterMap.filter(route => {
30 | if (route.component) {
31 | // Layout组件特殊处理
32 | if (route.component === 'Layout') {
33 | route.component = Layout
34 | } else {
35 | route.component = loadView(route.component)
36 | }
37 | }
38 | if (route.children != null && route.children && route.children.length) {
39 | route.children = filterAsyncRouter(route.children)
40 | }
41 | return true
42 | })
43 | }
44 |
45 | export const loadView = (view) => { // 路由懒加载
46 | return (resolve) => require([`@/views/${view}`], resolve)
47 | }
48 |
49 | export default {
50 | namespaced: true,
51 | state,
52 | mutations,
53 | actions
54 | }
55 |
--------------------------------------------------------------------------------
/extension/ui/src/store/modules/settings.js:
--------------------------------------------------------------------------------
1 | import variables from '@/styles/element-variables.scss'
2 | import defaultSettings from '@/settings'
3 |
4 | const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings
5 |
6 | const state = {
7 | theme: variables.theme,
8 | showSettings: showSettings,
9 | tagsView: tagsView,
10 | fixedHeader: fixedHeader,
11 | sidebarLogo: sidebarLogo
12 | }
13 |
14 | const mutations = {
15 | CHANGE_SETTING: (state, { key, value }) => {
16 | // eslint-disable-next-line no-prototype-builtins
17 | if (state.hasOwnProperty(key)) {
18 | state[key] = value
19 | }
20 | }
21 | }
22 |
23 | const actions = {
24 | changeSetting({ commit }, data) {
25 | commit('CHANGE_SETTING', data)
26 | }
27 | }
28 |
29 | export default {
30 | namespaced: true,
31 | state,
32 | mutations,
33 | actions
34 | }
35 |
--------------------------------------------------------------------------------
/extension/ui/src/store/modules/user.js:
--------------------------------------------------------------------------------
1 | import { oauth2Login, getInfo, oauth2CodeLogin } from '@/api/login'
2 | import { getToken, setToken, removeToken } from '@/utils/auth'
3 | import { resetRouter } from '@/router'
4 |
5 | const getDefaultState = () => {
6 | return {
7 | token: getToken(),
8 | name: '',
9 | avatar: '',
10 | roles: []
11 | }
12 | }
13 |
14 | const state = getDefaultState()
15 |
16 | const mutations = {
17 | RESET_STATE: (state) => {
18 | Object.assign(state, getDefaultState())
19 | },
20 | SET_TOKEN: (state, token) => {
21 | state.token = token
22 | },
23 | SET_NAME: (state, name) => {
24 | state.name = name
25 | },
26 | SET_AVATAR: (state, avatar) => {
27 | state.avatar = avatar
28 | }
29 | }
30 |
31 | const actions = {
32 | // user login
33 | oauth2Login({ commit }, userInfo) {
34 | const { username, password, captchaKey, captchaCode, loginType, type, code } = userInfo
35 | console.log(userInfo)
36 | return new Promise((resolve, reject) => {
37 | var data = new FormData()
38 | if (loginType) data.append('grant_type', loginType)
39 | // grant_type = password
40 | if (username) data.append('username', username.trim())
41 | if (password) data.append('password', password.trim())
42 | // grant_type = social
43 | if (type) data.append('type', type.trim())
44 | if (code) data.append('code', code.trim())
45 |
46 | // captcha params
47 | var params = {}
48 | if (captchaKey) params.captchaKey = captchaKey
49 | if (captchaCode) params.captchaCode = captchaCode
50 |
51 | oauth2Login(data, params).then(response => {
52 | const { access_token } = response
53 | commit('SET_TOKEN', access_token)
54 | setToken(access_token)
55 | resolve()
56 | }).catch(error => {
57 | reject(error)
58 | })
59 | })
60 | },
61 |
62 | oauth2CodeLogin({ commit }, formData) {
63 | return new Promise((resolve, reject) => {
64 | oauth2CodeLogin(formData).then(response => {
65 | const { access_token } = response
66 | commit('SET_TOKEN', access_token)
67 | setToken(access_token)
68 | resolve()
69 | }).catch(error => {
70 | console.log(error)
71 | reject(error)
72 | })
73 | })
74 | },
75 |
76 | // get user info
77 | getInfo({ commit, state }) {
78 | return new Promise((resolve, reject) => {
79 | getInfo().then(response => {
80 | const { nickname, avatar } = response
81 | commit('SET_NAME', nickname || '匿名用户')
82 | commit('SET_AVATAR', avatar)
83 | resolve(response)
84 | }).catch(error => {
85 | reject(error)
86 | })
87 | })
88 | },
89 |
90 | // user logout
91 | logout({ commit }) {
92 | return new Promise((resolve, reject) => {
93 | removeToken()
94 | resetRouter()
95 | commit('RESET_STATE')
96 | resolve()
97 | })
98 | },
99 |
100 | // remove token
101 | resetToken({ commit }) {
102 | return new Promise(resolve => {
103 | removeToken() // must remove token first
104 | commit('RESET_STATE')
105 | resolve()
106 | })
107 | },
108 |
109 | // save token
110 | saveToken({ commit }, token) {
111 | return new Promise(resolve => {
112 | commit('SET_TOKEN', token)
113 | setToken(token)
114 | resolve()
115 | })
116 | }
117 | }
118 |
119 | export default {
120 | namespaced: true,
121 | state,
122 | mutations,
123 | actions
124 | }
125 |
126 |
--------------------------------------------------------------------------------
/extension/ui/src/styles/element-ui.scss:
--------------------------------------------------------------------------------
1 | // cover some element-ui styles
2 |
3 | .el-breadcrumb__inner,
4 | .el-breadcrumb__inner a {
5 | font-weight: 400 !important;
6 | }
7 |
8 | .el-upload {
9 | input[type="file"] {
10 | display: none !important;
11 | }
12 | }
13 |
14 | .el-upload__input {
15 | display: none;
16 | }
17 |
18 | body .el-table th.gutter{ display: table-cell!important; }
19 |
20 | // to fixed https://github.com/ElemeFE/element/issues/2461
21 | .el-dialog {
22 | transform: none;
23 | left: 0;
24 | position: relative;
25 | margin: 0 auto;
26 | }
27 |
28 | // refine element ui upload
29 | .upload-container {
30 | .el-upload {
31 | width: 100%;
32 |
33 | .el-upload-dragger {
34 | width: 100%;
35 | height: 200px;
36 | }
37 | }
38 | }
39 |
40 | // dropdown
41 | .el-dropdown-menu {
42 | a {
43 | display: block
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/extension/ui/src/styles/element-variables.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * I think element-ui's default theme color is too light for long-term use.
3 | * So I modified the default color and you can modify it to your liking.
4 | **/
5 |
6 | /* theme color */
7 | $--color-primary: #1890ff;
8 | $--color-success: #13ce66;
9 | $--color-warning: #ffba00;
10 | $--color-danger: #ff4949;
11 | // $--color-info: #1E1E1E;
12 |
13 | $--button-font-weight: 400;
14 |
15 | // $--color-text-regular: #1f2d3d;
16 |
17 | $--border-color-light: #dfe4ed;
18 | $--border-color-lighter: #e6ebf5;
19 |
20 | $--table-border: 1px solid #dfe6ec;
21 |
22 | /* icon font path, required */
23 | $--font-path: "~element-ui/lib/theme-chalk/fonts";
24 |
25 | @import "~element-ui/packages/theme-chalk/src/index";
26 |
27 | // the :export directive is the magic sauce for webpack
28 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
29 | :export {
30 | theme: $--color-primary;
31 | }
--------------------------------------------------------------------------------
/extension/ui/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 | @import './mixin.scss';
3 | @import './transition.scss';
4 | @import './element-ui.scss';
5 | @import './sidebar.scss';
6 |
7 | body {
8 | height: 100%;
9 | -moz-osx-font-smoothing: grayscale;
10 | -webkit-font-smoothing: antialiased;
11 | text-rendering: optimizeLegibility;
12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
13 | background-color: rgb(240,242,245)
14 | }
15 |
16 | label {
17 | font-weight: 700;
18 | }
19 |
20 |
21 | html {
22 | height: 100%;
23 | box-sizing: border-box;
24 | }
25 |
26 | #app {
27 | height: 100%;
28 | }
29 |
30 | *,
31 | *:before,
32 | *:after {
33 | box-sizing: inherit;
34 | }
35 |
36 | a:focus,
37 | a:active {
38 | outline: none;
39 | }
40 |
41 | a,
42 | a:focus,
43 | a:hover {
44 | cursor: pointer;
45 | color: inherit;
46 | text-decoration: none;
47 | }
48 |
49 | div:focus {
50 | outline: none;
51 | }
52 |
53 | .clearfix {
54 | &:after {
55 | visibility: hidden;
56 | display: block;
57 | font-size: 0;
58 | content: " ";
59 | clear: both;
60 | height: 0;
61 | }
62 | }
63 |
64 | //main-container全局样式
65 | .app-container {
66 | padding: 50px;
67 | }
68 |
69 | .components-container {
70 | margin: 30px 50px;
71 | position: relative;
72 | }
73 |
74 | .pagination-container {
75 | margin-top: 30px;
76 | }
77 |
78 | .text-center {
79 | text-align: center
80 | }
81 |
82 | .sub-navbar {
83 | height: 50px;
84 | line-height: 50px;
85 | position: relative;
86 | width: 100%;
87 | text-align: right;
88 | padding-right: 20px;
89 | transition: 600ms ease position;
90 | background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
91 |
92 | .subtitle {
93 | font-size: 20px;
94 | color: #fff;
95 | }
96 |
97 | &.draft {
98 | background: #d0d0d0;
99 | }
100 |
101 | &.deleted {
102 | background: #d0d0d0;
103 | }
104 | }
105 |
106 | .link-type,
107 | .link-type:focus {
108 | color: #337ab7;
109 | cursor: pointer;
110 |
111 | &:hover {
112 | color: rgb(32, 160, 255);
113 | }
114 | }
115 |
116 | .filter-container {
117 | padding-bottom: 10px;
118 |
119 | .filter-item {
120 | display: inline-block;
121 | vertical-align: middle;
122 | margin-bottom: 10px;
123 | }
124 | }
125 |
126 | //refine vue-multiselect plugin
127 | .multiselect {
128 | line-height: 16px;
129 | }
130 |
131 | .multiselect--active {
132 | z-index: 1000 !important;
133 | }
134 |
135 |
136 | .el-card__header{
137 | padding: 15px!important;
138 | box-sizing: border-box;
139 | }
140 | .el-card__body{
141 | padding: 20px!important;
142 | }
143 |
144 | .el-card + .el-card{
145 | margin-top: 10px;
146 | }
147 |
--------------------------------------------------------------------------------
/extension/ui/src/styles/mixin.scss:
--------------------------------------------------------------------------------
1 | @mixin clearfix {
2 | &:after {
3 | content: "";
4 | display: table;
5 | clear: both;
6 | }
7 | }
8 |
9 | @mixin scrollBar {
10 | &::-webkit-scrollbar-track-piece {
11 | background: #d3dce6;
12 | }
13 |
14 | &::-webkit-scrollbar {
15 | width: 6px;
16 | }
17 |
18 | &::-webkit-scrollbar-thumb {
19 | background: #99a9bf;
20 | border-radius: 20px;
21 | }
22 | }
23 |
24 | @mixin relative {
25 | position: relative;
26 | width: 100%;
27 | height: 100%;
28 | }
29 |
--------------------------------------------------------------------------------
/extension/ui/src/styles/transition.scss:
--------------------------------------------------------------------------------
1 | // global transition css
2 |
3 | /* fade */
4 | .fade-enter-active,
5 | .fade-leave-active {
6 | transition: opacity 0.28s;
7 | }
8 |
9 | .fade-enter,
10 | .fade-leave-active {
11 | opacity: 0;
12 | }
13 |
14 | /* fade-transform */
15 | .fade-transform-leave-active,
16 | .fade-transform-enter-active {
17 | transition: all .5s;
18 | }
19 |
20 | .fade-transform-enter {
21 | opacity: 0;
22 | transform: translateX(-30px);
23 | }
24 |
25 | .fade-transform-leave-to {
26 | opacity: 0;
27 | transform: translateX(30px);
28 | }
29 |
30 | /* breadcrumb transition */
31 | .breadcrumb-enter-active,
32 | .breadcrumb-leave-active {
33 | transition: all .5s;
34 | }
35 |
36 | .breadcrumb-enter,
37 | .breadcrumb-leave-active {
38 | opacity: 0;
39 | transform: translateX(20px);
40 | }
41 |
42 | .breadcrumb-move {
43 | transition: all .5s;
44 | }
45 |
46 | .breadcrumb-leave-active {
47 | position: absolute;
48 | }
49 |
--------------------------------------------------------------------------------
/extension/ui/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | // sidebar
2 | $menuText:#bfcbd9;
3 | $menuActiveText:rgb(0, 123, 247);
4 | $subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
5 |
6 | $menuBg:#2b2f3a;
7 | $menuHover:#263445;
8 |
9 | $subMenuBg:#1f2d3d;
10 | $subMenuHover:#001528;
11 |
12 | $sideBarWidth: 210px;
13 |
14 | // the :export directive is the magic sauce for webpack
15 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
16 | :export {
17 | menuText: $menuText;
18 | menuActiveText: $menuActiveText;
19 | subMenuActiveText: $subMenuActiveText;
20 | menuBg: $menuBg;
21 | menuHover: $menuHover;
22 | subMenuBg: $subMenuBg;
23 | subMenuHover: $subMenuHover;
24 | sideBarWidth: $sideBarWidth;
25 | }
26 |
--------------------------------------------------------------------------------
/extension/ui/src/utils/auth.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const TokenKey = 'vue_admin_template_token'
4 |
5 | export function getToken() {
6 | return Cookies.get(TokenKey)
7 | }
8 |
9 | export function setToken(token) {
10 | return Cookies.set(TokenKey, token)
11 | }
12 |
13 | export function removeToken() {
14 | return Cookies.remove(TokenKey)
15 | }
16 |
--------------------------------------------------------------------------------
/extension/ui/src/utils/get-page-title.js:
--------------------------------------------------------------------------------
1 | import defaultSettings from '@/settings'
2 |
3 | const title = defaultSettings.title || 'Vue Admin Template'
4 |
5 | export default function getPageTitle(pageTitle) {
6 | if (pageTitle) {
7 | return `${pageTitle} - ${title}`
8 | }
9 | return `${title}`
10 | }
11 |
--------------------------------------------------------------------------------
/extension/ui/src/utils/open-window.js:
--------------------------------------------------------------------------------
1 | /**
2 | *Created by PanJiaChen on 16/11/29.
3 | * @param {string} url
4 | * @param {Sting} title
5 | * @param {Number} w
6 | * @param {Number} h
7 | */
8 | export default function openWindow(url, title, w, h) {
9 | // Fixes dual-screen position Most browsers Firefox
10 | const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left
11 | const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top
12 |
13 | const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width
14 | const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height
15 |
16 | const left = ((width / 2) - (w / 2)) + dualScreenLeft
17 | const top = ((height / 2) - (h / 2)) + dualScreenTop
18 | const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left)
19 |
20 | // Puts focus on the newWindow
21 | if (window.focus) {
22 | newWindow.focus()
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/extension/ui/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { MessageBox, Message } from 'element-ui'
3 | import store from '@/store'
4 | import { getToken } from '@/utils/auth'
5 |
6 | // create an axios instance
7 | const service = axios.create({
8 | baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
9 | // withCredentials: true, // send cookies when cross-domain requests
10 | timeout: 5000, // request timeout,
11 | paramsSerializer: function(params) {
12 | return require('qs').stringify(params, { arrayFormat: 'repeat' })
13 | },
14 | validateStatus: function(status) {
15 | return status >= 200 && status < 600 // default
16 | }
17 | })
18 |
19 | // request interceptor
20 | service.interceptors.request.use(
21 | config => {
22 | // do something before request is sent
23 | if (store.getters.token && !config.headers['Authorization']) {
24 | // let each request carry token
25 | // ['X-Token'] is a custom headers key
26 | // please modify it according to the actual situation
27 | config.headers['Authorization'] = 'Bearer ' + getToken()
28 | }
29 | return config
30 | },
31 | error => {
32 | // do something with request error
33 | console.log(error) // for debug
34 | return Promise.reject(error)
35 | }
36 | )
37 |
38 | // response interceptor
39 | service.interceptors.response.use(
40 | /**
41 | * If you want to get http information such as headers or status
42 | * Please return response => response
43 | */
44 |
45 | /**
46 | * Determine the request status by custom code
47 | * Here is just an example
48 | * You can also judge the status by HTTP Status Code
49 | */
50 | response => {
51 | if (response.status !== 200) {
52 | // Bad Request
53 | var errmsg = response.data.error_description || '未知错误, 请联系管理员'
54 | if (response.status === 400) {
55 | Message({
56 | message: errmsg,
57 | type: 'error',
58 | duration: 2 * 1000
59 | })
60 | }
61 | return Promise.reject(new Error(errmsg))
62 | }
63 | // OAuth2.0 不是通用数据格式
64 | if (response.data.code === undefined) {
65 | return response.data
66 | }
67 |
68 | // 通用数据
69 | const res = response.data
70 |
71 | // if the custom code is not 20000, it is judged as an error.
72 | if (res.code !== 'SUCCESS') {
73 | Message({
74 | message: res.msg || '未知错误',
75 | type: 'error',
76 | duration: 2 * 1000
77 | })
78 |
79 | // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
80 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
81 | // to re-login
82 | MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
83 | confirmButtonText: 'Re-Login',
84 | cancelButtonText: 'Cancel',
85 | type: 'warning'
86 | }).then(() => {
87 | store.dispatch('user/resetToken').then(() => {
88 | location.reload()
89 | })
90 | })
91 | }
92 | return Promise.reject(new Error(res.msg || 'Error'))
93 | } else {
94 | return res
95 | }
96 | },
97 | error => {
98 | console.log('err ', error) // for debug
99 | Message({
100 | message: error.message,
101 | type: 'error',
102 | duration: 5 * 1000
103 | })
104 | return Promise.reject(error)
105 | }
106 | )
107 |
108 | export default service
109 |
--------------------------------------------------------------------------------
/extension/ui/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by PanJiaChen on 16/11/18.
3 | */
4 |
5 | /**
6 | * @param {string} path
7 | * @returns {Boolean}
8 | */
9 | export function isExternal(path) {
10 | return /^(https?:|mailto:|tel:)/.test(path)
11 | }
12 |
13 | /**
14 | * @param {string} str
15 | * @returns {Boolean}
16 | */
17 | export function validUsername(str) {
18 | const valid_map = ['user', 'editor']
19 | return valid_map.indexOf(str.trim()) >= 0
20 | }
21 |
--------------------------------------------------------------------------------
/extension/ui/src/views/dashboard/components/MyCamera.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - 监控1
6 | - 监控2
7 | - 监控3
8 | - 监控4
9 |
10 |
11 |
12 |
17 |
56 |
--------------------------------------------------------------------------------
/extension/ui/src/views/dashboard/components/MyData.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - 80 %
5 | - 80 %
6 | - 80 %
7 | - 80 %
8 | - 80 %
9 | - 80 %
10 | - 80 %
11 | - 80 %
12 | - 80 %
13 |
14 |
15 |
16 |
17 |
22 |
23 |
84 |
--------------------------------------------------------------------------------
/extension/ui/src/views/dashboard/components/MyEchart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
135 |
--------------------------------------------------------------------------------
/extension/ui/src/views/dashboard/components/mixins/resize.js:
--------------------------------------------------------------------------------
1 | import { debounce } from '@/utils'
2 |
3 | export default {
4 | data() {
5 | return {
6 | $_sidebarElm: null,
7 | $_resizeHandler: null
8 | }
9 | },
10 | mounted() {
11 | this.$_resizeHandler = debounce(() => {
12 | if (this.chart) {
13 | this.chart.resize()
14 | }
15 | }, 100)
16 | this.$_initResizeEvent()
17 | this.$_initSidebarResizeEvent()
18 | },
19 | beforeDestroy() {
20 | this.$_destroyResizeEvent()
21 | this.$_destroySidebarResizeEvent()
22 | },
23 | // to fixed bug when cached by keep-alive
24 | // https://github.com/PanJiaChen/vue-element-admin/issues/2116
25 | activated() {
26 | this.$_initResizeEvent()
27 | this.$_initSidebarResizeEvent()
28 | },
29 | deactivated() {
30 | this.$_destroyResizeEvent()
31 | this.$_destroySidebarResizeEvent()
32 | },
33 | methods: {
34 | // use $_ for mixins properties
35 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
36 | $_initResizeEvent() {
37 | window.addEventListener('resize', this.$_resizeHandler)
38 | },
39 | $_destroyResizeEvent() {
40 | window.removeEventListener('resize', this.$_resizeHandler)
41 | },
42 | $_sidebarResizeHandler(e) {
43 | if (e.propertyName === 'width') {
44 | this.$_resizeHandler()
45 | }
46 | },
47 | $_initSidebarResizeEvent() {
48 | this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
49 | this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
50 | },
51 | $_destroySidebarResizeEvent() {
52 | this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/extension/ui/src/views/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
75 |
81 |
82 |
--------------------------------------------------------------------------------
/extension/ui/src/views/login/auth-redirect.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ errMsg }}, {{ time }} 秒后自动关闭
4 |
5 |
6 |
41 |
--------------------------------------------------------------------------------
/extension/ui/src/views/menu/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 角色列表
7 |
8 |
9 |
10 | 菜单列表
11 |
12 |
13 |
14 | 权限列表
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
43 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/App.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth;
2 |
3 | import org.mybatis.spring.annotation.MapperScan;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 |
7 | @MapperScan(basePackages = "cn.flizi.auth.mapper")
8 | @SpringBootApplication
9 | public class App {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(App.class, args);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/config/CorsConfig.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.core.annotation.Order;
6 | import org.springframework.http.HttpMethod;
7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
9 | import org.springframework.web.cors.CorsConfiguration;
10 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
11 | import org.springframework.web.filter.CorsFilter;
12 |
13 | @Configuration
14 | @Order(Integer.MIN_VALUE)
15 | public class CorsConfig extends WebSecurityConfigurerAdapter {
16 |
17 | @Bean
18 | public CorsFilter corsFilter() {
19 | CorsConfiguration config = new CorsConfiguration();
20 | config.addAllowedMethod("*");
21 | config.addAllowedHeader("*");
22 | config.addAllowedOrigin("*");
23 | UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
24 | configSource.registerCorsConfiguration("/**", config);
25 | return new CorsFilter(configSource);
26 | }
27 |
28 | @Override
29 | protected void configure(HttpSecurity http) throws Exception {
30 | http.requestMatchers().antMatchers(HttpMethod.OPTIONS, "/**");
31 | http.cors();
32 | }
33 | }
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/config/GlobalConfig.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.config;
2 |
3 | import cn.flizi.auth.properties.SocialProperties;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.ui.Model;
7 | import org.springframework.web.bind.annotation.ControllerAdvice;
8 | import org.springframework.web.bind.annotation.ModelAttribute;
9 |
10 |
11 | /**
12 | * 定义全局变量
13 | */
14 | @ControllerAdvice
15 | public class GlobalConfig {
16 |
17 | @Value("${baseinfo.title}")
18 | private String appName;
19 |
20 | @Value("${baseinfo.beian}")
21 | private String beian;
22 |
23 | @Autowired
24 | private SocialProperties socialProperties;
25 |
26 | @ModelAttribute
27 | public void addAttributes(Model model) {
28 | model.addAttribute("app_name", appName);
29 | model.addAttribute("beian", beian);
30 | model.addAttribute("wx_mp", socialProperties.getWxMp().getKey());
31 | model.addAttribute("wx_open", socialProperties.getWxOpen().getKey());
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/config/MessageLocalConfig.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.config;
2 |
3 | import org.springframework.context.MessageSource;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.context.support.ReloadableResourceBundleMessageSource;
7 |
8 | import java.util.Locale;
9 |
10 | /**
11 | * 中文 配置
12 | */
13 | @Configuration
14 | public class MessageLocalConfig {
15 |
16 | @Bean
17 | public MessageSource messageSource() {
18 | Locale.setDefault(Locale.CHINA);
19 | ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
20 | messageSource.addBasenames("classpath:messages_zh_CN");
21 | return messageSource;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/config/ResourceConfig.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.config;
2 |
3 | import cn.flizi.auth.security.RestAuthenticationEntryPoint;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.boot.autoconfigure.security.oauth2.authserver.AuthorizationServerProperties;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
9 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
10 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
11 |
12 | @EnableResourceServer
13 | @Configuration
14 | public class ResourceConfig extends ResourceServerConfigurerAdapter {
15 |
16 | @Autowired
17 | private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
18 |
19 | @Override
20 | public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
21 | resources.authenticationEntryPoint(restAuthenticationEntryPoint);
22 | }
23 |
24 | @Override
25 | public void configure(HttpSecurity http) throws Exception {
26 | http.requestMatchers().antMatchers("/user_base", "/user_info", "/api/**");
27 | http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint);
28 | super.configure(http);
29 | }
30 | }
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/config/SwaggerConfig.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import springfox.documentation.builders.ApiInfoBuilder;
6 | import springfox.documentation.builders.PathSelectors;
7 | import springfox.documentation.builders.RequestHandlerSelectors;
8 | import springfox.documentation.service.*;
9 | import springfox.documentation.spi.DocumentationType;
10 | import springfox.documentation.spi.service.contexts.SecurityContext;
11 | import springfox.documentation.spring.web.plugins.Docket;
12 | import springfox.documentation.swagger2.annotations.EnableSwagger2;
13 |
14 | import java.util.Collections;
15 | import java.util.List;
16 |
17 | @Configuration
18 | @EnableSwagger2
19 | public class SwaggerConfig {
20 |
21 | /**
22 | * 名称
23 | */
24 | private String name = "swagger";
25 |
26 | /**
27 | * 版本号
28 | */
29 | private String version = "0.1";
30 |
31 | /**
32 | * 前缀
33 | */
34 | private String prefix = "/";
35 |
36 | /**
37 | * 扫描包的基本前缀
38 | */
39 | private String basePackage = "cn.flizi";
40 |
41 |
42 | @Bean
43 | public Docket api() {
44 | return new Docket(DocumentationType.SWAGGER_2)
45 | .apiInfo(apiInfo())
46 | .select()
47 | .apis(RequestHandlerSelectors.basePackage(basePackage))
48 | .build()
49 | .securitySchemes(securitySchemes())
50 | .securityContexts(securityContexts());
51 | }
52 |
53 | private List securitySchemes() {
54 | return Collections.singletonList(new ApiKey("Authorization", "Authorization", "header"));
55 | }
56 |
57 |
58 | private List securityContexts() {
59 | return Collections.singletonList(
60 | SecurityContext.builder()
61 | .securityReferences(defaultAuth())
62 | .forPaths(PathSelectors.regex("/.*"))
63 | .build()
64 | );
65 | }
66 |
67 | private List defaultAuth() {
68 | AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
69 | AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
70 | authorizationScopes[0] = authorizationScope;
71 | return Collections.singletonList(
72 | new SecurityReference("Authorization", authorizationScopes));
73 | }
74 |
75 | private ApiInfo apiInfo() {
76 | return new ApiInfoBuilder()
77 | .title(name)
78 | .version(version)
79 | .build();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/config/WebSecurityConfig.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.config;
2 |
3 | import cn.flizi.auth.security.filter.CaptchaValidationFilter;
4 | import cn.flizi.auth.security.social.SocialAuthenticationProvider;
5 | import cn.flizi.auth.security.social.SocialCodeAuthenticationFilter;
6 | import cn.flizi.auth.security.social.SocialDetailsService;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 | import org.springframework.security.authentication.AuthenticationManager;
11 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
12 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
13 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
14 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
15 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
16 | import org.springframework.security.core.userdetails.UserDetailsService;
17 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
18 |
19 | /**
20 | * Spring Security 配置
21 | */
22 | @Configuration
23 | @EnableWebSecurity
24 | @EnableGlobalMethodSecurity(prePostEnabled = true)
25 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
26 |
27 | @Autowired
28 | private UserDetailsService userDetailsService;
29 | @Autowired
30 | private SocialDetailsService socialDetailsService;
31 | @Autowired
32 | private CaptchaValidationFilter captchaValidationFilter;
33 |
34 | /**
35 | * 注入 Spring 容器中, 在授权服务器中密码模式下,验证用户密码正确性, 以及第三方授权码验证
36 | */
37 | @Bean
38 | @Override
39 | public AuthenticationManager authenticationManagerBean() throws Exception {
40 | return super.authenticationManagerBean();
41 | }
42 |
43 | @Override
44 | protected void configure(AuthenticationManagerBuilder auth) throws Exception {
45 | auth.authenticationProvider(new SocialAuthenticationProvider(socialDetailsService));
46 | auth.userDetailsService(userDetailsService);
47 | }
48 |
49 | /**
50 | * 配置 HttpSecurity
51 | */
52 | @Override
53 | protected void configure(HttpSecurity http) throws Exception {
54 | SocialCodeAuthenticationFilter socialCodeAuthenticationFilter = new SocialCodeAuthenticationFilter();
55 | socialCodeAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
56 |
57 | http
58 | .csrf().disable()
59 | .formLogin()
60 | .loginPage("/login").and()
61 | .authorizeRequests()
62 | // 对外开放接口
63 | .antMatchers("/login", "/social", "/reset", "/captcha", "/signup",
64 | "/sms", "/auth-redirect", "/weixin-code").permitAll()
65 | // 静态资源
66 | .antMatchers("/static/**").permitAll()
67 | // swagger api
68 | .antMatchers("/v2/api-docs", "/swagger-ui/**", "/swagger-resources/**").permitAll()
69 | .anyRequest().authenticated()
70 | .and()
71 | .addFilterBefore(captchaValidationFilter, UsernamePasswordAuthenticationFilter.class)
72 | .addFilterBefore(socialCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
73 | .httpBasic();
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/entity/Captcha.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.entity;
2 |
3 | import java.util.Date;
4 |
5 | public class Captcha {
6 | private String captchaId;
7 | private String key;
8 | private String code;
9 | private Date createTime;
10 |
11 | public String getCaptchaId() {
12 | return captchaId;
13 | }
14 |
15 | public void setCaptchaId(String captchaId) {
16 | this.captchaId = captchaId;
17 | }
18 |
19 | public String getKey() {
20 | return key;
21 | }
22 |
23 | public void setKey(String key) {
24 | this.key = key;
25 | }
26 |
27 | public String getCode() {
28 | return code;
29 | }
30 |
31 | public void setCode(String code) {
32 | this.code = code;
33 | }
34 |
35 | public Date getCreateTime() {
36 | return createTime;
37 | }
38 |
39 | public void setCreateTime(Date createTime) {
40 | this.createTime = createTime;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/entity/Sms.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.entity;
2 |
3 | import java.util.Date;
4 |
5 | public class Sms {
6 | private String smsId;
7 | private String phone;
8 | private String code;
9 | private Date createTime;
10 |
11 | public String getSmsId() {
12 | return smsId;
13 | }
14 |
15 | public void setSmsId(String smsId) {
16 | this.smsId = smsId;
17 | }
18 |
19 | public String getPhone() {
20 | return phone;
21 | }
22 |
23 | public void setPhone(String phone) {
24 | this.phone = phone;
25 | }
26 |
27 | public String getCode() {
28 | return code;
29 | }
30 |
31 | public void setCode(String code) {
32 | this.code = code;
33 | }
34 |
35 | public Date getCreateTime() {
36 | return createTime;
37 | }
38 |
39 | public void setCreateTime(Date createTime) {
40 | this.createTime = createTime;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/entity/User.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.entity;
2 |
3 | public class User {
4 | private String userId;
5 | private String password;
6 | private String phone;
7 | private String email;
8 | private String wxOpenid;
9 | private String wxUnionid;
10 |
11 | public String getUserId() {
12 | return userId;
13 | }
14 |
15 | public void setUserId(String userId) {
16 | this.userId = userId;
17 | }
18 |
19 | public String getPassword() {
20 | return password;
21 | }
22 |
23 | public void setPassword(String password) {
24 | this.password = password;
25 | }
26 |
27 | public String getPhone() {
28 | return phone;
29 | }
30 |
31 | public void setPhone(String phone) {
32 | this.phone = phone;
33 | }
34 |
35 | public String getEmail() {
36 | return email;
37 | }
38 |
39 | public void setEmail(String email) {
40 | this.email = email;
41 | }
42 |
43 | public String getWxOpenid() {
44 | return wxOpenid;
45 | }
46 |
47 | public void setWxOpenid(String wxOpenid) {
48 | this.wxOpenid = wxOpenid;
49 | }
50 |
51 | public String getWxUnionid() {
52 | return wxUnionid;
53 | }
54 |
55 | public void setWxUnionid(String wxUnionid) {
56 | this.wxUnionid = wxUnionid;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/mapper/CaptchaMapper.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.mapper;
2 |
3 | import cn.flizi.auth.entity.Captcha;
4 | import org.apache.ibatis.annotations.*;
5 |
6 | @Mapper
7 | public interface CaptchaMapper {
8 |
9 | @Options(useGeneratedKeys = true, keyProperty = "captchaId", keyColumn = "captcha_id")
10 | @Insert("INSERT INTO captcha (`key`, code) VALUES (#{key}, #{code})")
11 | void insert(Captcha captcha);
12 |
13 | @Select("SELECT * FROM captcha WHERE `key`=#{key} ORDER BY create_time DESC LIMIT 1")
14 | Captcha getCaptchaByKey(String key);
15 |
16 | @Delete("DELETE FROM captcha WHERE `key`=#{key}")
17 | void delete(String key);
18 | }
19 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/mapper/SmsMapper.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.mapper;
2 |
3 | import cn.flizi.auth.entity.Sms;
4 | import org.apache.ibatis.annotations.Insert;
5 | import org.apache.ibatis.annotations.Mapper;
6 | import org.apache.ibatis.annotations.Options;
7 | import org.apache.ibatis.annotations.Select;
8 |
9 | @Mapper
10 | public interface SmsMapper {
11 |
12 | @Options(useGeneratedKeys = true, keyProperty = "smsId", keyColumn = "sms_id")
13 | @Insert("INSERT INTO sms (phone, code)" +
14 | "VALUES (#{phone},#{code})")
15 | void insert(Sms sms);
16 |
17 | @Select("select * from sms where phone=#{phone} order by create_time desc limit 1")
18 | Sms getCodeByPhone(String phone);
19 | }
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/mapper/UserMapper.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.mapper;
2 |
3 | import cn.flizi.auth.entity.User;
4 | import org.apache.ibatis.annotations.*;
5 | import org.apache.ibatis.annotations.Param;
6 |
7 | @Mapper
8 | public interface UserMapper {
9 |
10 | @Select("SELECT * FROM user WHERE phone = #{name} or email = #{name}")
11 | User loadUserByUsername(String name);
12 |
13 | @Select("SELECT * FROM user WHERE `user_id` = #{userId}")
14 | User loadUserByUserId(String userId);
15 |
16 | @Select("SELECT * FROM user WHERE ${column} = #{value}")
17 | User loadUserByColumn(@Param("column") String column, @Param("value") String value);
18 |
19 | @Options(useGeneratedKeys = true, keyProperty = "userId", keyColumn = "user_id")
20 | @Insert("INSERT INTO user (password, phone, email, wx_openid, wx_unionid)" +
21 | "VALUES (#{password},#{phone},#{email}, #{wxOpenid}, #{wxUnionid})")
22 | void insert(User user);
23 |
24 | @Update("update user set password=#{password} where phone=#{phone}")
25 | void updatePassword(@Param("phone") String phone, @Param("password") String password);
26 |
27 | @Update("update user set phone=#{phone} where `user_id`=#{userId}")
28 | void updatePhone(@Param("userId") String userId, @Param("phone") String phone);
29 |
30 | @Update("update user set wx_openid=#{openid} where `user_id`=#{userId}")
31 | void updateWxOpenId(@Param("userId") String userId, @Param("openid") String openid);
32 |
33 | @Update("update user set wx_unionid=#{unionid} where `user_id`=#{userId}")
34 | void updateWxUnionId(@Param("userId") String userId, @Param("unionid") String unionid);
35 | }
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/properties/CaptchaProperties.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.properties;
2 |
3 |
4 | import org.springframework.boot.context.properties.ConfigurationProperties;
5 | import org.springframework.context.annotation.Configuration;
6 |
7 | @Configuration
8 | @ConfigurationProperties(prefix = "captcha")
9 | public class CaptchaProperties {
10 | private Boolean enable = true;
11 | private Integer length = 4;
12 | private String baseStr = "023456789";
13 |
14 | public Boolean getEnable() {
15 | return enable;
16 | }
17 |
18 | public void setEnable(Boolean enable) {
19 | this.enable = enable;
20 | }
21 |
22 | public Integer getLength() {
23 | return length;
24 | }
25 |
26 | public void setLength(Integer length) {
27 | this.length = length;
28 | }
29 |
30 | public String getBaseStr() {
31 | return baseStr;
32 | }
33 |
34 | public void setBaseStr(String baseStr) {
35 | this.baseStr = baseStr;
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return "CaptchaProperties{" +
41 | "enable=" + enable +
42 | ", length=" + length +
43 | ", baseStr='" + baseStr + '\'' +
44 | '}';
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/properties/SmsProperties.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.properties;
2 |
3 |
4 | import org.springframework.boot.context.properties.ConfigurationProperties;
5 | import org.springframework.context.annotation.Configuration;
6 |
7 | @Configuration
8 | @ConfigurationProperties(prefix = "sms")
9 | public class SmsProperties {
10 | private Boolean enable = true;
11 | private String secretId;
12 | private String secretKey;
13 | private String appId;
14 | private String sign;
15 | private String templateId;
16 |
17 | public Boolean getEnable() {
18 | return enable;
19 | }
20 |
21 | public void setEnable(Boolean enable) {
22 | this.enable = enable;
23 | }
24 |
25 | public String getSecretId() {
26 | return secretId;
27 | }
28 |
29 | public void setSecretId(String secretId) {
30 | this.secretId = secretId;
31 | }
32 |
33 | public String getSecretKey() {
34 | return secretKey;
35 | }
36 |
37 | public void setSecretKey(String secretKey) {
38 | this.secretKey = secretKey;
39 | }
40 |
41 | public String getAppId() {
42 | return appId;
43 | }
44 |
45 | public void setAppId(String appId) {
46 | this.appId = appId;
47 | }
48 |
49 | public String getSign() {
50 | return sign;
51 | }
52 |
53 | public void setSign(String sign) {
54 | this.sign = sign;
55 | }
56 |
57 | public String getTemplateId() {
58 | return templateId;
59 | }
60 |
61 | public void setTemplateId(String templateId) {
62 | this.templateId = templateId;
63 | }
64 |
65 | @Override
66 | public String toString() {
67 | return "SmsProperties{" +
68 | "enable=" + enable +
69 | ", secretId='" + secretId + '\'' +
70 | ", secretKey='" + secretKey + '\'' +
71 | ", appId='" + appId + '\'' +
72 | ", sign='" + sign + '\'' +
73 | ", templateId='" + templateId + '\'' +
74 | '}';
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/properties/SocialProperties.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.properties;
2 |
3 |
4 | import org.springframework.boot.context.properties.ConfigurationProperties;
5 | import org.springframework.context.annotation.Configuration;
6 |
7 | @Configuration
8 | @ConfigurationProperties(prefix = "social")
9 | public class SocialProperties {
10 |
11 | private WxMp wxMp = new WxMp();
12 | private WxOpen wxOpen = new WxOpen();
13 |
14 | public WxMp getWxMp() {
15 | return wxMp;
16 | }
17 |
18 | public void setWxMp(WxMp wxMp) {
19 | this.wxMp = wxMp;
20 | }
21 |
22 | public WxOpen getWxOpen() {
23 | return wxOpen;
24 | }
25 |
26 | public void setWxOpen(WxOpen wxOpen) {
27 | this.wxOpen = wxOpen;
28 | }
29 |
30 | public static class WxMp {
31 | private String key;
32 | private String secret;
33 |
34 | public String getKey() {
35 | return key;
36 | }
37 |
38 | public void setKey(String key) {
39 | this.key = key;
40 | }
41 |
42 | public String getSecret() {
43 | return secret;
44 | }
45 |
46 | public void setSecret(String secret) {
47 | this.secret = secret;
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return "WxOpen{" +
53 | "key='" + key + '\'' +
54 | ", secret='" + secret + '\'' +
55 | '}';
56 | }
57 | }
58 |
59 | public static class WxOpen {
60 | private String key;
61 | private String secret;
62 |
63 | public String getKey() {
64 | return key;
65 | }
66 |
67 | public void setKey(String key) {
68 | this.key = key;
69 | }
70 |
71 | public String getSecret() {
72 | return secret;
73 | }
74 |
75 | public void setSecret(String secret) {
76 | this.secret = secret;
77 | }
78 |
79 | @Override
80 | public String toString() {
81 | return "WxOpen{" +
82 | "key='" + key + '\'' +
83 | ", secret='" + secret + '\'' +
84 | '}';
85 | }
86 | }
87 |
88 | @Override
89 | public String toString() {
90 | return "SocialProperties{" +
91 | "wxMp=" + wxMp +
92 | ", wxOpen=" + wxOpen +
93 | '}';
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/security/AuthUser.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.security;
2 |
3 | import org.springframework.security.core.GrantedAuthority;
4 | import org.springframework.security.core.userdetails.User;
5 |
6 | import java.util.Collection;
7 |
8 | /**
9 | * Security 的 User 类 和 用户表 User 类重名, 这里集成一次以作区分
10 | * Security 的 User 类 username 对应 用户表 user_id 字段
11 | * 扩展 字段 phone
12 | */
13 | public class AuthUser extends User {
14 |
15 | private String phone;
16 |
17 | private Integer tenant;
18 |
19 | public void setPhone(String phone) {
20 | this.phone = phone;
21 | }
22 |
23 | public String getPhone() {
24 | return phone;
25 | }
26 |
27 | public Integer getTenant() {
28 | return tenant;
29 | }
30 |
31 |
32 |
33 | public AuthUser(String username, String password, boolean enabled, Collection extends GrantedAuthority> authorities) {
34 | super(username, password, enabled, true, true, true, authorities);
35 | }
36 |
37 | public AuthUser(String username, Collection extends GrantedAuthority> authorities) {
38 | super(username, "N/A", true, true, true, true, authorities);
39 | }
40 |
41 | public AuthUser(String username, Integer tenant, Collection extends GrantedAuthority> authorities) {
42 | super(username, "N/A", true, true, true, true, authorities);
43 | this.tenant = tenant;
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/security/CaptchaService.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.security;
2 |
3 | public interface CaptchaService {
4 |
5 | void set(String key, String value, long expiresInSeconds);
6 |
7 | boolean exists(String key);
8 |
9 | void delete(String key);
10 |
11 | String get(String key);
12 |
13 | default boolean check(String key, String value) {
14 | if (!exists(key)) {
15 | return false;
16 | }
17 | String s = get(key);
18 | boolean b = s != null && s.equalsIgnoreCase(value);
19 | if (b) {
20 | delete(key);
21 | }
22 | return b;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/security/CustomWebResponseExceptionTranslator.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.security;
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.http.ResponseEntity;
5 | import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
6 | import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
7 |
8 | import java.util.HashMap;
9 |
10 | public class CustomWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
11 |
12 | @Override
13 | public ResponseEntity translate(Exception e) throws Exception {
14 | HashMap result = new HashMap<>();
15 | result.put("code", "ERROR");
16 | result.put("msg", e.getMessage());
17 | return new ResponseEntity<>(result, HttpStatus.OK);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/security/ExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.security;
2 |
3 | import cn.flizi.core.util.R;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.validation.BindException;
6 | import org.springframework.validation.FieldError;
7 | import org.springframework.web.HttpRequestMethodNotSupportedException;
8 | import org.springframework.web.bind.annotation.RestControllerAdvice;
9 | import org.springframework.web.servlet.NoHandlerFoundException;
10 |
11 | import javax.servlet.http.HttpServletRequest;
12 | import java.util.List;
13 |
14 | @RestControllerAdvice
15 | public class ExceptionHandler {
16 | @Autowired
17 | private HttpServletRequest request;
18 |
19 | @org.springframework.web.bind.annotation.ExceptionHandler(HttpRequestMethodNotSupportedException.class)
20 | public R handler(HttpRequestMethodNotSupportedException e) {
21 | return R.errMsg("不支持 " + e.getMethod() + " 请求方式");
22 | }
23 |
24 | @org.springframework.web.bind.annotation.ExceptionHandler(NoHandlerFoundException.class)
25 | public R handler(NoHandlerFoundException e) {
26 | return R.errMsg("不存在 " + e.getRequestURL() + " 路径");
27 | }
28 |
29 | @org.springframework.web.bind.annotation.ExceptionHandler(BindException.class)
30 | public R validExceptionHandler(BindException e) {
31 | StringBuilder message = new StringBuilder();
32 | List fieldErrors = e.getBindingResult().getFieldErrors();
33 | for (FieldError error : fieldErrors) {
34 | message.append(error.getField()).append(error.getDefaultMessage()).append(",");
35 | }
36 | message = new StringBuilder(message.substring(0, message.length() - 1));
37 | String basePath = request.getScheme()
38 | + "://" + request.getServerName() + ":" + request.getServerPort() + "/swagger-ui/index.html";
39 | return R.error(basePath, message.toString());
40 | }
41 |
42 | @org.springframework.web.bind.annotation.ExceptionHandler(Exception.class)
43 | public R handler(Exception e) {
44 | return R.errMsg(e.getMessage());
45 | }
46 | }
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/security/JwtUserAuthenticationConverter.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.security;
2 |
3 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
4 | import org.springframework.security.core.Authentication;
5 | import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
6 |
7 | import java.util.Map;
8 |
9 | /**
10 | * 生成 JWT 的时候加入 tenant 字段, 解析jwt的时候, 将 tenant放入 Authentication中
11 | */
12 | public class JwtUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
13 |
14 | public static final String TENANT_ID = "tenant";
15 |
16 |
17 | public Map convertUserAuthentication(Authentication authentication) {
18 | Map response = (Map) super.convertUserAuthentication(authentication);
19 | response.put(TENANT_ID, 1);
20 | return response;
21 | }
22 |
23 | @Override
24 | public Authentication extractAuthentication(Map map) {
25 | Authentication authentication = super.extractAuthentication(map);
26 | if (authentication != null) {
27 | String userId = authentication.getName();
28 | Integer tenantId = (Integer) map.get(TENANT_ID);
29 | AuthUser principal = new AuthUser(userId, tenantId, authentication.getAuthorities());
30 | return new UsernamePasswordAuthenticationToken(principal, "N/A", authentication.getAuthorities());
31 | }
32 | return null;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/security/RestAuthenticationEntryPoint.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.security;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.security.core.AuthenticationException;
5 | import org.springframework.security.web.AuthenticationEntryPoint;
6 | import org.springframework.stereotype.Component;
7 | import org.springframework.web.servlet.HandlerExceptionResolver;
8 |
9 | import javax.servlet.ServletException;
10 | import javax.servlet.http.HttpServletRequest;
11 | import javax.servlet.http.HttpServletResponse;
12 | import java.io.IOException;
13 |
14 | @Component("restAuthenticationEntryPoint")
15 | public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
16 |
17 | @Autowired
18 | private HandlerExceptionResolver handlerExceptionResolver;
19 |
20 | @Override
21 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
22 | handlerExceptionResolver.resolveException(request, response, null, exception);
23 | }
24 | }
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/security/social/SocialCodeAuthenticationFilter.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.security.social;
2 |
3 | import cn.flizi.auth.security.social.SocialCodeAuthenticationToken;
4 | import org.springframework.security.core.Authentication;
5 | import org.springframework.security.core.AuthenticationException;
6 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
7 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
8 |
9 | import javax.servlet.http.HttpServletRequest;
10 | import javax.servlet.http.HttpServletResponse;
11 |
12 | public class SocialCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
13 |
14 | private String typeParameter = "type";
15 | private String codeParameter = "code";
16 | private String redirectParameter = "redirect_uri";
17 |
18 | public void setTypeParameter(String typeParameter) {
19 | this.typeParameter = typeParameter;
20 | }
21 |
22 | public void setCodeParameter(String codeParameter) {
23 | this.codeParameter = codeParameter;
24 | }
25 |
26 | public void setRedirectParameter(String redirectParameter) {
27 | this.redirectParameter = redirectParameter;
28 | }
29 |
30 | public SocialCodeAuthenticationFilter() {
31 | super(new AntPathRequestMatcher("/social", "POST"));
32 | }
33 |
34 | public Authentication attemptAuthentication(HttpServletRequest request,
35 | HttpServletResponse response) throws AuthenticationException {
36 |
37 | String type = obtainUsername(request);
38 | String code = obtainPassword(request);
39 | String redirect = obtainRedirect(request);
40 |
41 | if (type == null) {
42 | type = "";
43 | }
44 |
45 | if (code == null) {
46 | code = "";
47 | }
48 |
49 | type = type.trim();
50 | code = code.trim();
51 |
52 | SocialCodeAuthenticationToken authRequest = new SocialCodeAuthenticationToken(type, code, redirect);
53 |
54 | setDetails(request, authRequest);
55 |
56 | return this.getAuthenticationManager().authenticate(authRequest);
57 | }
58 |
59 | protected String obtainPassword(HttpServletRequest request) {
60 | return request.getParameter(codeParameter);
61 | }
62 |
63 | protected String obtainUsername(HttpServletRequest request) {
64 | return request.getParameter(typeParameter);
65 | }
66 |
67 | protected String obtainRedirect(HttpServletRequest request) {
68 | return request.getParameter(redirectParameter);
69 | }
70 |
71 |
72 | protected void setDetails(HttpServletRequest request,
73 | SocialCodeAuthenticationToken authRequest) {
74 | authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/security/social/SocialCodeAuthenticationToken.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.security.social;
2 |
3 | import org.springframework.security.authentication.AbstractAuthenticationToken;
4 | import org.springframework.security.core.GrantedAuthority;
5 | import org.springframework.security.core.SpringSecurityCoreVersion;
6 |
7 | import java.util.Collection;
8 |
9 | public class SocialCodeAuthenticationToken extends AbstractAuthenticationToken {
10 |
11 | private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
12 |
13 | // ~ Instance fields
14 | // ================================================================================================
15 |
16 | private final Object type;
17 | private Object code;
18 |
19 | public String getRedirectUri() {
20 | return redirectUri;
21 | }
22 |
23 | public void setRedirectUri(String redirectUri) {
24 | this.redirectUri = redirectUri;
25 | }
26 |
27 | private String redirectUri;
28 |
29 | // ~ Constructors
30 | // ===================================================================================================
31 | public SocialCodeAuthenticationToken(String type, String code, String redirectUri) {
32 | super(null);
33 | this.type = type;
34 | this.code = code;
35 | this.redirectUri = redirectUri;
36 | setAuthenticated(false);
37 | }
38 | /**
39 | * This constructor can be safely used by any code that wishes to create a
40 | * UsernamePasswordAuthenticationToken
, as the {@link #isAuthenticated()}
41 | * will return false
.
42 | */
43 | public SocialCodeAuthenticationToken(Object type, Object code) {
44 | super(null);
45 | this.type = type;
46 | this.code = code;
47 | setAuthenticated(false);
48 | }
49 |
50 | /**
51 | * This constructor should only be used by AuthenticationManager
or
52 | * AuthenticationProvider
implementations that are satisfied with
53 | * producing a trusted (i.e. {@link #isAuthenticated()} = true
)
54 | * authentication token.
55 | *
56 | * @param type
57 | * @param code
58 | * @param authorities
59 | */
60 | public SocialCodeAuthenticationToken(Object type, Object code, Collection extends GrantedAuthority> authorities) {
61 | super(authorities);
62 | this.type = type;
63 | this.code = code;
64 | super.setAuthenticated(true); // must use super, as we override
65 | }
66 |
67 | // ~ Methods
68 | // ========================================================================================================
69 |
70 | public Object getCredentials() {
71 | return this.code;
72 | }
73 |
74 | public Object getPrincipal() {
75 | return this.type;
76 | }
77 |
78 | public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
79 | if (isAuthenticated) {
80 | throw new IllegalArgumentException(
81 | "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
82 | }
83 |
84 | super.setAuthenticated(false);
85 | }
86 |
87 | @Override
88 | public void eraseCredentials() {
89 | super.eraseCredentials();
90 | code = null;
91 | }
92 | }
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/security/social/SocialCodeTokenGranter.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.security.social;
2 |
3 | import org.springframework.security.authentication.AbstractAuthenticationToken;
4 | import org.springframework.security.authentication.AccountStatusException;
5 | import org.springframework.security.authentication.AuthenticationManager;
6 | import org.springframework.security.authentication.BadCredentialsException;
7 | import org.springframework.security.core.Authentication;
8 | import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
9 | import org.springframework.security.oauth2.provider.*;
10 | import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
11 | import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
12 |
13 | import java.util.LinkedHashMap;
14 | import java.util.Map;
15 |
16 | /**
17 | * 该方法是扩展了Security OAuth来实现的 Rest API 请求
18 | */
19 | public class SocialCodeTokenGranter extends AbstractTokenGranter {
20 |
21 | private static final String GRANT_TYPE = "social";
22 |
23 | private final AuthenticationManager authenticationManager;
24 |
25 | public SocialCodeTokenGranter(AuthenticationManager authenticationManager,
26 | AuthorizationServerTokenServices tokenServices,
27 | ClientDetailsService clientDetailsService,
28 | OAuth2RequestFactory requestFactory) {
29 | this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
30 | }
31 |
32 | protected SocialCodeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
33 | ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
34 | super(tokenServices, clientDetailsService, requestFactory, grantType);
35 | this.authenticationManager = authenticationManager;
36 | }
37 |
38 | @Override
39 | protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
40 |
41 | Map parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
42 | String type = parameters.get("type");
43 | String code = parameters.get("code");
44 | String redirectUri = parameters.get("redirect_uri");
45 | // Protect from downstream leaks of code
46 | parameters.remove("code");
47 |
48 | Authentication userAuth = new SocialCodeAuthenticationToken(type, code, redirectUri);
49 | ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
50 | try {
51 | userAuth = authenticationManager.authenticate(userAuth);
52 | } catch (AccountStatusException | BadCredentialsException ase) {
53 | // If the type/code are wrong the spec says we should send 400/invalid grant
54 | //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
55 | throw new InvalidGrantException(ase.getMessage());
56 | }
57 |
58 | if (userAuth == null || !userAuth.isAuthenticated()) {
59 | throw new InvalidGrantException("Could not authenticate user: " + type);
60 | }
61 |
62 | OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
63 | return new OAuth2Authentication(storedOAuth2Request, userAuth);
64 | }
65 | }
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/security/social/SocialDetailsService.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.security.social;
2 |
3 | import org.springframework.security.core.userdetails.UserDetails;
4 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
5 |
6 | public interface SocialDetailsService {
7 |
8 | UserDetails loadUserBySocial(String type, String code, String redirectUri) throws UsernameNotFoundException;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/security/social/SocialUser.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.security.social;
2 |
3 | import org.springframework.security.core.AuthenticatedPrincipal;
4 |
5 | public class SocialUser implements AuthenticatedPrincipal {
6 |
7 | private String accessToken;
8 |
9 | private String username;
10 |
11 | private String nickname;
12 |
13 | private String avatar;
14 |
15 | @Override
16 | public String getName() {
17 | return username;
18 | }
19 |
20 | public String getAccessToken() {
21 | return accessToken;
22 | }
23 |
24 | public void setAccessToken(String accessToken) {
25 | this.accessToken = accessToken;
26 | }
27 |
28 | public String getUsername() {
29 | return username;
30 | }
31 |
32 | public void setUsername(String username) {
33 | this.username = username;
34 | }
35 |
36 | public String getNickname() {
37 | return nickname;
38 | }
39 |
40 | public void setNickname(String nickname) {
41 | this.nickname = nickname;
42 | }
43 |
44 | public String getAvatar() {
45 | return avatar;
46 | }
47 |
48 | public void setAvatar(String avatar) {
49 | this.avatar = avatar;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/oauth2/src/main/java/cn/flizi/auth/service/MysqlCaptchaService.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth.service;
2 |
3 | import cn.flizi.auth.entity.Captcha;
4 | import cn.flizi.auth.mapper.CaptchaMapper;
5 | import cn.flizi.auth.security.CaptchaService;
6 | import org.springframework.stereotype.Service;
7 | import org.springframework.util.StringUtils;
8 |
9 | import java.util.Date;
10 |
11 | @Service
12 | public class MysqlCaptchaService implements CaptchaService {
13 | private final CaptchaMapper captchaMapper;
14 |
15 | public MysqlCaptchaService(CaptchaMapper captchaMapper) {
16 | this.captchaMapper = captchaMapper;
17 | }
18 |
19 | @Override
20 | public void set(String key, String value, long expiresInSeconds) {
21 | Captcha captcha = new Captcha();
22 | captcha.setKey(key);
23 | captcha.setCode(value);
24 | captchaMapper.insert(captcha);
25 | }
26 |
27 | @Override
28 | public boolean exists(String key) {
29 | if (!StringUtils.hasLength(key)) {
30 | return false;
31 | }
32 | Captcha captcha = captchaMapper.getCaptchaByKey(key);
33 | if (captcha == null) {
34 | return false;
35 | }
36 | long now = new Date().getTime();
37 | if ((now - captcha.getCreateTime().getTime()) > 60 * 1000) {
38 | return false;
39 | }
40 | return true;
41 | }
42 |
43 | @Override
44 | public void delete(String key) {
45 | captchaMapper.delete(key);
46 | }
47 |
48 | @Override
49 | public String get(String key) {
50 | Captcha captchaByKey = captchaMapper.getCaptchaByKey(key);
51 | return captchaByKey.getCode();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/oauth2/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | # https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
2 | mybatis:
3 | configuration:
4 | map-underscore-to-camel-case: true
5 |
6 | spring:
7 | profiles:
8 | active: pro
9 | datasource:
10 | driver-class-name: com.mysql.cj.jdbc.Driver
11 | username: root
12 | password: root
13 | url: jdbc:mysql://127.0.0.1:3306/auth?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
14 |
15 | security:
16 | oauth2:
17 | authorization:
18 | jwt:
19 | key-value: hello_world
20 |
21 | logging:
22 | level:
23 | root: INFO
24 | org.springframework.web: DEBUG
25 | org.springframework.security: DEBUG
26 | org.springframework.security.oauth2: DEBUG
27 | org.springframework.boot.autoconfigure: DEBUG
28 |
29 | baseinfo:
30 | title: 浙江xxxx科技
31 | beian: 浙ICP备xxxx号
32 |
33 |
34 | # 图形验证码配置
35 | captcha:
36 | enable: true # 启用
37 | base-str: '0123' # 随机字符
38 | length: 4 # 长度
39 |
40 | # 手机登录配置(腾讯云)
41 | sms:
42 | enable: true # 启用
43 | secretId: 'xxxx'
44 | secretKey: 'xxxx'
45 | appId: 'xxx'
46 | sign: 'xxx'
47 | templateId: 'xxx'
48 |
49 | dingtalk:
50 | access_token: xxx
51 |
52 | # 社会账号登录
53 | social:
54 | wx-mp: # 微信公众平台
55 | key: 'xxx'
56 | secret: 'xxx'
57 | wx-open: # 微信开放平台
58 | key: 'xxx'
59 | secret: 'xxxx'
--------------------------------------------------------------------------------
/oauth2/src/main/resources/public/static/base.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
4 |
5 | a {
6 | text-decoration: none;
7 | color: #4D4D4D;
8 | }
9 |
10 |
11 | input:-webkit-autofill {
12 | -webkit-box-shadow: 0 0 0px 32px #fff inset;
13 | /**通过边框阴影遮挡住背景*/
14 | -webkit-text-fill-color: #333;
15 | /*自动填充内容的文本颜色*/
16 | }
17 |
18 | #Wrapper {
19 | border-radius: 3px;
20 | }
21 |
22 | .Label {
23 | display: flex;
24 | border-bottom: 1px solid #ebebeb;
25 | }
26 |
27 | .Input {
28 | -webkit-box-flex: 1;
29 | -ms-flex: 1 1;
30 | flex: 1 1;
31 | padding: 0;
32 | overflow: hidden;
33 | font-family: inherit;
34 | font-size: inherit;
35 | font-weight: inherit;
36 | background: transparent;
37 | resize: none;
38 | border: none;
39 |
40 | outline: none;
41 |
42 | width: 100%;
43 | height: 48px;
44 | padding: 0;
45 | color: #8590a6;
46 | }
47 |
48 | .Button {
49 | display: inline-block;
50 | text-align: center;
51 | cursor: pointer;
52 | background: none;
53 | border-radius: 3px;
54 | margin: 0;
55 | padding: 0;
56 | border: none;
57 | font-size: 16px;
58 | line-height: 16px;
59 | color: #409eff;
60 | }
61 |
62 | .Button2 {
63 | color:#333;
64 | outline: none;
65 | }
66 |
67 | .Header {
68 | position: relative;
69 | display: flex;
70 | justify-content: center;
71 | align-items: center;
72 | flex-direction: column;
73 | color: #409eff;
74 | font-weight: 700;
75 | height: 80px;
76 | padding: 0;
77 | border-radius: 5px 5px 0 0;
78 | }
79 |
80 |
81 | .Header-title {
82 | font-size: 25px;
83 | }
84 |
85 | .Header-subTitle {
86 | font-size: 16px;
87 | }
88 |
89 | .Form {
90 | margin-top: 20px;
91 | padding: 20px 20px;
92 | }
93 |
94 | .Form-submitButton {
95 | width: 100%;
96 | height: 40px;
97 | margin-top: 32px;
98 |
99 | display: inline-block;
100 | padding: 0 16px;
101 | font-size: 14px;
102 | line-height: 32px;
103 | text-align: center;
104 | cursor: pointer;
105 | background: none;
106 | border: 1px solid;
107 | border-radius: 3px;
108 | color: #fff;
109 | font-size: 16px;
110 | background-color: #06f;
111 | }
112 |
113 |
114 | @media screen and (min-width:500px) {
115 | #Wrapper {
116 | margin: 70px auto;
117 | max-width: 350px;
118 | padding-bottom: 20px;
119 | }
120 | .Form {
121 | box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
122 | }
123 | }
124 |
125 | .Tools {
126 | display: flex;
127 | justify-content: space-between;
128 | padding: 20px 16px;
129 | font-weight: 700;
130 | color: #409eff
131 | }
132 |
133 |
134 | .Footer {
135 | text-align: center;
136 | margin-top: 60px;
137 | color: #333;
138 | }
--------------------------------------------------------------------------------
/oauth2/src/main/resources/templates/auth-redirect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/oauth2/src/main/resources/templates/confirm_access.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 | 授权 - XXX
13 |
14 |
15 |
16 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/oauth2/src/main/resources/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 | 首页 - XXX
13 |
19 |
20 |
21 |
22 |
47 |
48 |
49 |
50 |
51 |
69 |
70 |
--------------------------------------------------------------------------------
/oauth2/src/test/java/cn/flizi/auth/AppTests.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.auth;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class AppTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/oauth2/src/test/java/cn/flizi/ext/rbac/ExtRestApiTest.java:
--------------------------------------------------------------------------------
1 | package cn.flizi.ext.rbac;
2 |
3 |
4 | import cn.flizi.auth.App;
5 | import cn.flizi.core.util.R;
6 | import lombok.extern.log4j.Log4j2;
7 | import org.assertj.core.api.Assertions;
8 | import org.junit.jupiter.api.Test;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.security.test.context.support.WithMockUser;
12 |
13 | import java.util.List;
14 |
15 | @Log4j2
16 | @SpringBootTest(classes = App.class)
17 | class ExtRestApiTest {
18 |
19 | @Autowired
20 | private ExtRestApi extRestApi;
21 |
22 | @Test
23 | public void contextLoads() throws Exception {
24 | Assertions.assertThat(extRestApi).isNotNull();
25 | }
26 |
27 | @Test
28 | @WithMockUser(username = "1", authorities = {"sys:menu:tree"})
29 | public void sysMenuTree() throws Exception {
30 | R> listR = extRestApi.sysMenuTree();
31 | log.info(listR);
32 | Assertions.assertThat(listR).isNotNull();
33 | }
34 | }
--------------------------------------------------------------------------------